Logo Search packages:      
Sourcecode: qps version File versions

proc.C

// proc.C
//
// This program is free software. See the file COPYING for details.
// Author: Mattias Engdegård, 1997-1999

#include <stdio.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <dirent.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <sched.h>

#include "qps.h"
#include "proc.h"
#include "svec.C"
#include "uidstr.h"
#include "ttystr.h"
#include "wchan.h"
#include "details.h"

#ifdef SOLARIS
#include <sys/swap.h>
#include <sys/sysinfo.h>
#include <sys/mkdev.h>
#include <limits.h>
#endif

//#define FAKE_SMP 4    // for SMP debugging on UP machines

// socket states, from <linux/net.h> and touched to avoid name collisions
enum {
  SSFREE = 0,                 /* not allocated        */
  SSUNCONNECTED,        /* unconnected to any socket  */
  SSCONNECTING,               /* in process of connecting   */
  SSCONNECTED,                /* connected to socket        */
  SSDISCONNECTING       /* in process of disconnecting      */
};

const char *procdir = "/proc";

int Procinfo::page_k_shift;

Procinfo::Procinfo(int proc_pid)
    : refcnt(1)
{
    details = 0;
    children = 0;
    fd_files = 0;
    maps = 0;
#ifdef LINUX
    sock_inodes = 0;
    socks_current = FALSE;
    usocks_current = FALSE;
    per_cpu_times = 0;
#endif

    environ = 0;
    envblock = 0;
    if(readproc(proc_pid) < 0)
      pid = -1;         // invalidate object, will be deleted

    selected = FALSE;
    hidekids = FALSE;
}

Procinfo::~Procinfo()
{
    if(details) {
      details->process_gone();
      details = 0;
    }
    delete environ;
    if(envblock)
      free(envblock);
#ifdef LINUX
    delete sock_inodes;
    delete[] per_cpu_times;
#endif
    if(maps) {
      maps->purge();
      delete maps;
    }
    if(fd_files) {
      fd_files->purge();
      delete fd_files;
    }
    delete children;
}

// miscellaneous static initializations
void Procinfo::init_static()
{
#ifdef SOLARIS
    if(!kc) {
      kc = kstat_open();
      if(!kc) {
          perror("kstat_open");
          exit(1);
      }
    }
#endif

#ifdef LINUX
    socks.setAutoDelete(TRUE);
    usocks.setAutoDelete(TRUE);
#endif

    page_k_shift = 0;
    for(int j = getpagesize(); j > 1024; j >>= 1)
      page_k_shift++;
}

// return number of bytes read if ok, -1 if failed
int Procinfo::read_file(char *name, void *buf, int max)
{
    int fd = open(name, O_RDONLY);
    if(fd < 0) return -1;
    int r = read(fd, buf, max);
    close(fd);
    return r;
}

static inline bool isprintable(unsigned char c)
{
    // assume, somewhat naïvely, that all latin-1 characters are printable
    return (c >= 0x20 && c < 0x7f) || c >= 0xa0;
}

// replace unprintables by spaces
static void make_printable(char *s)
{
    while(*s) {
       if(!isprintable(*s)) *s = ' ';
       ++s;
    }
}

#ifdef LINUX

int Procinfo::readproc(int proc_pid)
{
    char path[256];
    char buf[256];
    char sbuf[4096];          // should be enough to acommodate /proc/X/stat
    char cmdbuf[MAX_CMD_LEN];

    pid = proc_pid;

    sprintf(path, "%s/%d", procdir, pid);
    // read /proc/XX/cmdline
    strcpy(buf, path);
    strcat(buf, "/cmdline");
    int cmdlen;
    if((cmdlen = read_file(buf, cmdbuf, MAX_CMD_LEN - 1)) < 0) return -1;
    if(cmdlen == 0) {
      cmdline = "";
    } else {
      for(int i = 0; i < cmdlen; i++)
          if(!cmdbuf[i]) cmdbuf[i] = ' ';
      int j = cmdlen - 1;
      while(j >= 0 && cmdbuf[j] == ' ')
          j--;
      cmdbuf[j + 1] = '\0';
      make_printable(cmdbuf);
      cmdline = cmdbuf;
    }
    
    // read /proc/XX/stat
    strcpy(buf, path);
    strcat(buf, "/stat");
    int statlen;
    if((statlen = read_file(buf, sbuf, sizeof(sbuf) - 1)) <= 0) return -1;
    sbuf[statlen] = '\0';
    char *p = strrchr(sbuf, ')');
    *p = '\0';                // split in two parts
    comm = strchr(sbuf, '(') + 1;
    
    //
    // Not all values from /proc/#/stat are interesting; the ones left out
    // have been retained in comments to see where they should go, in case
    // they are needed again.
    //
    // In Linux 2.2.x, timeout has been removed, and the signal information
    // here is obsolete (/proc/#/status has real-time signal info).
    //
    // There are undocumented values after wchan, unused so far:
    // nswap            pages swapped out since process started
    // cnswap           nswap of children
    // exit_signal      (2.2.x) signal sent to parent when process exits
    // (The latter could provide a way to detect cloned processes since
    //  they usually have exit_signal != SIGCHLD, but I prefer waiting
    //  for real TIDs before implementing thread support.)
    //
    long stime, cstime;
    sscanf(p + 2, "%c %d %d %d %d %d %lu %lu %lu %lu %lu "
         "%ld %ld %ld %ld %d %d %*s %*s %lu %*s %*s %*s %*s %*s %*s %*s %*s "
         "%*s %*s %*s %*s %lu",
         &state, &ppid, &pgrp, &session, &tty, &tpgid,
         &flags, &minflt, &cminflt, &majflt, &cmajflt,
         &utime, &stime, &cutime, &cstime, &priority, &nice,
         /* timeout, itrealvalue */
         &starttime,
         /* vsize */
         /* rss */
         /* rlim, startcode, endcode, startstack kstkesp kstkeip,
            signal, blocked, sigignore, sigcatch */
         &wchan);
    
    utime += stime;           // we make no user/system time distinction 
    cutime += cstime;
    
    // read /proc/XX/statm
    strcpy(buf, path);
    strcat(buf, "/statm");
    if((statlen = read_file(buf, sbuf, sizeof(sbuf) - 1)) <= 0) return -1;
    sbuf[statlen] = '\0';
    sscanf(sbuf, "%lu %lu %lu %lu %lu %lu %lu",
         &size, &resident, &share, &trs, &lrs, &drs, &dt);
    size <<= page_k_shift;
    resident <<= page_k_shift;
    share <<= page_k_shift;
    trs <<= page_k_shift;
    lrs <<= page_k_shift;
    drs <<= page_k_shift;
    
    pmem = 100.0 * resident / mem_total;
    
#ifdef MOSIX // LINUX and MOSIX
    // Read /proc/XX/where
    strcpy(buf, path);
    strcat(buf, "/where");
    if((statlen = read_file(buf, sbuf, sizeof(sbuf) - 1)) <= 0) 
      where = -1;
    else { 
      sbuf[statlen] = '\0';
      sscanf(sbuf, "%d", &where);
    }
    // Read /proc/XX/cantmove
    strcpy(buf, path);
    strcat(buf, "/cantmove");
    if((statlen = read_file(buf, sbuf, sizeof(sbuf) - 1)) > 0) {
      sbuf[statlen] = '\0';
      p = strchr(sbuf, '\n');
      if(p) {
          *p = '\0';
          cantmove = sbuf;
      }
    }
    // Read /proc/XX/nmigs
    strcpy(buf, path);
    strcat(buf, "/nmigs");
    if((statlen = read_file(buf, sbuf, sizeof(sbuf) - 1)) <= 0) 
      nmigs = -1; 
    else {
      sbuf[statlen] = '\0';
      sscanf(sbuf, "%d", &nmigs);
    }
    // Read /proc/XX/lock
    strcpy(buf, path);
    strcat(buf, "/lock");
    if((statlen = read_file(buf, sbuf, sizeof(sbuf) - 1)) <= 0)
      locked = -1;
    else {
      sbuf[statlen] = '\0';
      sscanf(sbuf, "%d", &locked);
    }

    // See if this is a remote job
    // Read /proc/mosix/remote/xx/statm
    char path2[256];
    sprintf(path2, "%s/mosix/remote/%d", procdir, pid);
    // There are only two files to read: stats and from
    // (statm is identical to /proc/xx/statm).
    strcpy(buf, path2);
    strcat(buf, "/stats");
    if((statlen = read_file(buf, sbuf, sizeof(sbuf) - 1)) > 0) {
      isremote = TRUE;  // This process is a visitor

      // The following variables are available, but are not used at the
      // moment, so they are merely placeholders right now.
      char state2;
      long nswap, cnswap, rss, vsize;
      if(sscanf(sbuf, "utime=%ld cutime=%ld nice=%d state=%c vsize=%ld"
                    " rss=%ld nswap=%ld cnswap=%ld",
              &utime, &cutime, &nice, &state2, &vsize, &rss,
              &nswap, &cnswap) != 8)
          return -1;

      // read "from"
      strcpy(buf, path2);
      strcat(buf, "/from");
      if((statlen = read_file(buf, sbuf, sizeof(sbuf) - 1)) <= 0) return -1;
      sbuf[statlen] = '\0';
      if(sscanf(sbuf, "%d", &from) != 1) return -1;
    
      // comm is parsed wrong (contains "remote(xx)")
      sscanf(comm, "remote(%d)", &remotepid);
      comm = cmdline; // grab comm from there...
    } else {
      isremote = FALSE;
      remotepid = -1;
      from = -1;
    }
    if(from > 0)
      sprintf(buf, "%d>", from);
    else if(where > 0) 
      sprintf(buf, ">%d", where);
    else
      strcpy(buf, "-");
    migr = buf;

#endif // MOSIX
    
    // read /proc/XX/status
    strcpy(buf, path);
    strcat(buf, "/status");
    if((statlen = read_file(buf, sbuf, sizeof(sbuf) - 1)) <= 0) return -1;
    sbuf[statlen] = '\0';
    if(!(p = strstr(sbuf, "Uid:")))
      return -1;
    sscanf(p, "Uid: %d %d %d %d Gid: %d %d %d %d",
         &uid, &euid, &suid, &fsuid,
         &gid, &egid, &sgid, &fsgid);

    which_cpu = 0;
    per_cpu_times = 0;
    if(num_cpus > 1) {
      strcpy(buf, path);
      strcat(buf, "/cpu");
      if( (statlen = read_file(buf, sbuf, sizeof(sbuf) - 1)) <= 0)
          return -1;
      sbuf[statlen] = '\0';
      per_cpu_times = new unsigned long[num_cpus];
      p = sbuf;
      for(unsigned cpu = 0; cpu < num_cpus; cpu++) {
          p = strchr(p, '\n');
          if (!p) {
            for(cpu = 0; cpu < num_cpus; cpu++)
                per_cpu_times[cpu] = 0;
            break;
          }
          p++;
          unsigned long utime, stime;
          sscanf(p, "%*s %lu %lu", &utime, &stime);
          per_cpu_times[cpu] = utime + stime;
      }
    }

    gettimeofday(&tv, 0);
    policy = -1;        // will get it when needed
    rtprio = -1;        // ditto
  
    return pid;
}

#endif // LINUX

#ifdef SOLARIS
int Procinfo::readproc(int proc_pid)
{
    char path[256];

    pid = proc_pid;

    sprintf(path, "%s/%d/psinfo", procdir, proc_pid);
    psinfo_t psi;
    if(read_file(path, (void *)&psi, sizeof(psi)) < (int)sizeof(psi))
      return -1;

    sprintf(path, "%s/%d/usage", procdir, proc_pid);
    prusage_t pru;
    if(read_file(path, (void *)&pru, sizeof(pru)) < (int)sizeof(pru))
      return -1;

    uid = psi.pr_uid;
    euid = psi.pr_euid;
    gid = psi.pr_gid;
    egid = psi.pr_egid;

    make_printable(psi.pr_psargs);
    cmdline = psi.pr_psargs;

    state = psi.pr_lwp.pr_sname;
    comm = (state == 'Z') ? "<zombie>" : psi.pr_fname;
    ppid = psi.pr_ppid;
    pgrp = psi.pr_pgid;
    session = psi.pr_sid;
    tty = psi.pr_ttydev;      // type?
    flags = psi.pr_flag;
    const int ns_ticks = 1000000000 / HZ;
    utime = psi.pr_time.tv_sec * HZ + psi.pr_time.tv_nsec / ns_ticks;
    cutime = psi.pr_ctime.tv_sec * HZ + psi.pr_ctime.tv_nsec / ns_ticks;
    priority = psi.pr_lwp.pr_pri;
    nice = psi.pr_lwp.pr_nice;
    if(Qps::normalize_nice)
      nice -= NZERO;
    starttime = (psi.pr_start.tv_sec - boot_time) * HZ
              + psi.pr_start.tv_nsec / ns_ticks;
    wchan = psi.pr_lwp.pr_wchan;
    minflt = pru.pr_minf;
    majflt = pru.pr_majf;
    size = psi.pr_size;
    resident = psi.pr_rssize;
    nthreads = psi.pr_nlwp;
    which_cpu = psi.pr_lwp.pr_onpro;
    env_ofs = psi.pr_envp;

    // pr_pctcpu and pr_pctmem are scaled so that 1.0 is stored as 0x8000.
    // We rescale pcpu so a CPU-bound process is shown as 100%. (This means
    // that pcpu may exceed 100% with several LWPs.)
    pcpu = psi.pr_pctcpu * (1 / 327.68) * num_cpus;
    pmem = psi.pr_pctmem * (1 / 327.68);

    gettimeofday(&tv, 0);
    rtprio = -1;        // ditto
    policy_name[0] = psi.pr_lwp.pr_clname[0];
    policy_name[1] = psi.pr_lwp.pr_clname[1];

    return pid;
}
#endif // SOLARIS

float Procinfo::loadavg[] = {0.0, 0.0, 0.0};
int Procinfo::mem_total = 0;
int Procinfo::mem_free = 0;
#ifdef LINUX
int Procinfo::mem_shared = 0;
int Procinfo::mem_buffers = 0;
int Procinfo::mem_cached = 0;
#endif
int Procinfo::swap_total = 0;
int Procinfo::swap_free = 0;
unsigned *Procinfo::cpu_times_vec = 0;
unsigned *Procinfo::old_cpu_times_vec = 0;
long Procinfo::boot_time = 0;
#ifdef LINUX
QIntDict<Sockinfo> Procinfo::socks(17);
bool Procinfo::socks_current = FALSE;
QIntDict<UnixSocket> Procinfo::usocks(17);
bool Procinfo::usocks_current = FALSE;
#endif
unsigned int Procinfo::num_cpus = 0;
unsigned int Procinfo::old_num_cpus = 0;
#ifdef SOLARIS
kstat_ctl_t *Procinfo::kc = 0;
#endif
#ifdef MOSIX
bool Procinfo::mosix_running;
#endif

#ifdef LINUX
// just grab the load averages
void Procinfo::read_loadavg()
{
    char path[80];
    char buf[512];
    strcpy(path, procdir);
    strcat(path, "/loadavg");
    int n;
    if((n = read_file(path, buf, sizeof(buf) - 1)) <= 0) {
      fprintf(stderr,   "qps: Cannot open /proc/loadavg"
                    " (make sure /proc is mounted)\n");
      exit(1);
    }
    buf[n] = '\0';
    sscanf(buf, "%f %f %f", &loadavg[0], &loadavg[1], &loadavg[2]);
}
#endif // LINUX

#ifdef SOLARIS
static float getscaled(kstat_t *ks, const char *name)
{
    // load avgs are scaled by 256
    kstat_named_t *kn = (kstat_named_t *)kstat_data_lookup(ks, (char *)name);
    return kn ? kn->value.ui32 * (1 / 256.0) : 0.0;
}

void Procinfo::read_loadavg()
{
    kstat_chain_update(kc);

    kstat_t *ks = kstat_lookup(kc, "unix", 0, "system_misc");
    if(!ks || kstat_read(kc, ks, 0) == -1) {
      perror("kstat_lookup/read");
      exit(1);
    }

    loadavg[0] = getscaled(ks, "avenrun_1min");
    loadavg[1] = getscaled(ks, "avenrun_5min");
    loadavg[2] = getscaled(ks, "avenrun_15min");

    // we might as well get the boot time too since it's in the same kstat
    // (not that it is going to change)
    kstat_named_t *kn;
    kn = (kstat_named_t *)kstat_data_lookup(ks, "boot_time");
    if(kn)
      boot_time = kn->value.ui32;
}
#endif // SOLARIS

#ifdef LINUX
// read information common to all processes
void Procinfo::read_common()
{
    char path[80];
    char buf[4096 + 1];

    // read memory info
    strcpy(path, procdir);
    strcat(path, "/meminfo");
    int n;
    if((n = read_file(path, buf, sizeof(buf) - 1)) <= 0) return;
    buf[n] = '\0';

    // Skip the old /meminfo cruft, making this work in post-2.1.42 kernels
    // as well.  (values are now in kB)
    char *p = strstr(buf, "MemTotal:");
    sscanf(p, "MemTotal: %d kB\nMemFree: %d kB\nMemShared: %d kB\nBuffers: %d"
         " kB\nCached: %d kB\n",
         &mem_total, &mem_free, &mem_shared, &mem_buffers,
         &mem_cached);
    p = strstr(buf, "SwapTotal:");
    sscanf(p, "SwapTotal: %d kB\nSwapFree: %d kB\n",
         &swap_total, &swap_free);

    // read system status
    strcpy(path, procdir);
    strcat(path, "/stat");
    if((n = read_file(path, buf, sizeof(buf) - 1)) <= 0) return;
    buf[n] = '\0';

    old_num_cpus = num_cpus;
    if(!num_cpus) {
      // count cpus
#ifdef FAKE_SMP
      num_cpus = 4;
#else
      char *p;
      p = strstr(buf, "cpu");
      while(p < buf + sizeof(buf) - 4 && strncmp(p, "cpu", 3) == 0) {
          num_cpus++;
          if(strncmp(p, "cpu0", 4) == 0)
            num_cpus--;
          p = strchr(p, '\n');
          if(p)
            p++;
      }
#endif
      cpu_times_vec = new unsigned[CPUTIMES * num_cpus];
      old_cpu_times_vec = new unsigned[CPUTIMES * num_cpus];
    }

    for(unsigned cpu = 0; cpu < num_cpus; cpu++)
      for(int i = 0; i < CPUTIMES; i++)
          old_cpu_times(cpu, i) = cpu_times(cpu, i);

    if(num_cpus == 1) {
      sscanf(buf, "cpu %u %u %u %u",
             &cpu_times(0, CPUTIME_USER), &cpu_times(0, CPUTIME_NICE),
             &cpu_times(0, CPUTIME_SYSTEM), &cpu_times(0, CPUTIME_IDLE));
    } else {
#ifdef FAKE_SMP
      sscanf(buf, "cpu %u %u %u %u",
             &cpu_times(0, CPUTIME_USER), &cpu_times(0, CPUTIME_NICE),
             &cpu_times(0, CPUTIME_SYSTEM), &cpu_times(0, CPUTIME_IDLE));
      for(unsigned cpu = 1; cpu < num_cpus; cpu++) {
          for(int i = 0; i < CPUTIMES; i++)
            cpu_times(cpu, i) = cpu_times(0, i);
      }
#else
      // SMP
      for(unsigned cpu = 0; cpu < num_cpus; cpu++) {
          char cpu_buf[10];
          sprintf(cpu_buf, "cpu%d", cpu);
          if((p = strstr(buf, cpu_buf)) != 0) {
            sscanf(p, "%*s %u %u %u %u",
                   &cpu_times(cpu, CPUTIME_USER), 
                   &cpu_times(cpu, CPUTIME_NICE),
                   &cpu_times(cpu, CPUTIME_SYSTEM), 
                   &cpu_times(cpu, CPUTIME_IDLE));
          } else {
            fprintf(stderr, "Error reading info for cpu %d\n", cpu);
            abort();
          }
      }
#endif
    }

    // 2.0.x kernels (at least up to 2.0.33) have an SMP bug that reports
    // cpu_time(CPUTIME_IDLE) incorrectly, since it doesn't take the number of
    // cpus into account. This is fixed in 2.1.x kernels, but a workaround
    // should really be here.

    p = strstr(buf, "btime") + 6;
    sscanf(p, "%lu", &boot_time);
}
#endif // LINUX

#ifdef SOLARIS
void Procinfo::read_common()
{
    // memory info: this is easy - just use sysconf

    mem_total = sysconf(_SC_PHYS_PAGES) << page_k_shift;
    mem_free = sysconf(_SC_AVPHYS_PAGES) << page_k_shift;

    // get swap info: somewhat trickier - we have to count all swap spaces

    int nswaps = swapctl(SC_GETNSWP, 0);
    swaptbl_t *st = (swaptbl_t *)malloc(sizeof(int)
                              + nswaps * sizeof(swapent_t));
    st->swt_n = nswaps;
    // We are not interested in the paths, just the values, so we allocate
    // one scratch buffer for all paths to keep swapctl happy.
    char path_buf[PATH_MAX + 1];
    for(int i = 0; i < nswaps; i++)
      st->swt_ent[i].ste_path = path_buf;
    swapctl(SC_LIST, st);
    // count the swap spaces
    swap_total = swap_free = 0;
    for(int i = 0; i < nswaps; i++) {
      swap_total += st->swt_ent[i].ste_pages;
      swap_free += st->swt_ent[i].ste_free;
    }
    swap_total <<= page_k_shift;
    swap_free <<= page_k_shift;
    free(st);

    if(cpu_times_vec) {
      if(old_cpu_times_vec) free(old_cpu_times_vec);
      old_cpu_times_vec = cpu_times_vec;
      cpu_times_vec = (unsigned *)malloc(sizeof(unsigned)
                                 * num_cpus * CPUTIMES);
    }
    old_num_cpus = num_cpus;

    // cpu states: are stored as kstats named "cpu_statN", where N is the
    // cpu number. Unfortunately, the cpu numbers are not guessable so we
    // sweep the kstat chain for all of them, assuming (foolishly?)
    // that they are in order.

    kstat_chain_update(kc);
    int cpu = 0;
    for(kstat_t *ks = kc->kc_chain; ks; ks = ks->ks_next) {
      if(strncmp(ks->ks_name, "cpu_stat", 8) == 0) {
          if(kstat_read(kc, ks, NULL) == -1) {
            perror("kstat_read");
            exit(1);
          }
          cpu_stat_t *cs = (cpu_stat_t *)ks->ks_data;
          if(cpu + 1 >= (int)num_cpus) {
            num_cpus = cpu + 1;
            cpu_times_vec = (unsigned *)realloc(cpu_times_vec,
                                        num_cpus * CPUTIMES
                                        * sizeof(unsigned));
          }
          cpu_times(cpu, CPUTIME_USER) = cs->cpu_sysinfo.cpu[CPU_USER];
          cpu_times(cpu, CPUTIME_SYSTEM) = cs->cpu_sysinfo.cpu[CPU_KERNEL];
          cpu_times(cpu, CPUTIME_WAIT) = cs->cpu_sysinfo.cpu[CPU_WAIT];
          cpu_times(cpu, CPUTIME_IDLE) = cs->cpu_sysinfo.cpu[CPU_IDLE];
          cpu++;
      }
    }
}
#endif // SOLARIS

#ifdef MOSIX
void Procinfo::check_for_mosix()
{
    char path[256];
    strcpy(path, procdir);
    strcat(path, "/mosix");
    DIR *d = opendir(path);
    if(d) {
      closedir(d);
      mosix_running = TRUE;
      return;
    }
    mosix_running = FALSE;
}

Svec<int> Procinfo::mosix_nodes()
{
    Svec<int> nodes;
    char path[256];
    strcpy(path, procdir);
    strcat(path, "/mosix/nodes");
    DIR *d = opendir(path);
    if(d) {
      struct dirent *e;
      while((e = readdir(d)) != 0) {
          int num;
          if(sscanf(e->d_name, "%d", &num) == 1)
            nodes.add(num);
      }
      closedir(d);
    }
    return nodes;
}
#endif // MOSIX

int Procinfo::get_policy()
{
    if(policy == -1)
      policy = sched_getscheduler(pid);
    return policy;
}

int Procinfo::get_rtprio()
{
    if(rtprio == -1) {
      struct sched_param p;
      if(sched_getparam(pid, &p) == 0)
          rtprio = p.sched_priority;
    }
    return rtprio;
}

#ifdef LINUX
void Procinfo::read_fd(int fdnum, char *path)
{
    int len;
    char buf[80];
    struct stat sb;

    // The fd mode is contained in the link permission bits
    if(lstat(path, &sb) < 0)
      return;
    int mode = 0;
    if(sb.st_mode & 0400) mode |= OPEN_READ;
    if(sb.st_mode & 0200) mode |= OPEN_WRITE;

    if( (len = readlink(path, buf, sizeof(buf) - 1)) > 0) {
      buf[len] = '\0';
      unsigned long dev, ino;
      if((buf[0] == '[' // Linux 2.0 style /proc/fd
          && sscanf(buf, "[%lx]:%lu", &dev, &ino) == 2
          && dev == 0)
         || sscanf(buf, "socket:[%lu]", &ino) > 0) { // Linux 2.1
          Sockinfo *si = Procinfo::socks[ino];
          if(si) {
            // a TCP or UDP socket
            sock_inodes->add(SockInode(fdnum, ino));
            QString s;
            s.sprintf("%sp socket %d",
                    si->proto == Sockinfo::TCP ? "tc" : "ud", ino);
            fd_files->add(new Fileinfo(fdnum, s, mode));
            return;
          } else {
            // maybe a unix domain socket?
            read_usockets();
            UnixSocket *us = Procinfo::usocks[ino];
            if(us) {
                QString s;
                char *tp = "?", *st = "?";
                switch(us->type) {
                case SOCK_STREAM: tp = "stream"; break;
                case SOCK_DGRAM: tp = "dgram"; break;
                }
                switch(us->state) {
                case SSFREE: st = "free"; break;
                case SSUNCONNECTED: st = "unconn"; break;
                case SSCONNECTING: st = "connecting"; break;
                case SSCONNECTED: st = "connected"; break;
                case SSDISCONNECTING: st = "disconn"; break;
                }
                s.sprintf("unix domain socket %d (%s, %s) ",
                        ino, tp, st);
                s.append(us->name);
                fd_files->add(new Fileinfo(fdnum, s, mode));
                return;
            }
          }
      }
      // assume fds will be read in increasing order
      fd_files->add(new Fileinfo(fdnum, buf, mode));
    }
}
#endif // LINUX

#ifdef SOLARIS
void Procinfo::read_fd(int fdnum, char *path)
{
    struct stat sb;
    if(lstat(path, &sb) < 0) {
      // The file has been closed, or we could really not stat it despite
      // having it open (could be a fd passed from another process).

      fd_files->add(new Fileinfo(fdnum, "(no info available)"));
      return;
    }
    // We could in principle find out more about the fd, such as its mode
    // (RDONLY, RDWR etc) and flags, but it's messy. pfiles uses an agent lwp
    // for this, but I don't know how to do it.
    QString s;
    const char *n;
    switch(sb.st_mode & S_IFMT) {
    case S_IFCHR:
      // if it is a tty, we might know its real name
      if(sb.st_rdev != (dev_t)-1) {
          QString t = Ttystr::name(sb.st_rdev);
          if(t[0] != '?') {
            s = "/dev/";
            s.append(t);
            break;
          }
      }
      s.sprintf("char device %u:%u",
              (unsigned)major(sb.st_rdev), (unsigned)minor(sb.st_rdev));
      break;

    case S_IFBLK:
      s.sprintf("block device %u:%u",
              (unsigned)major(sb.st_rdev), (unsigned)minor(sb.st_rdev));
      break;

    case S_IFLNK:
      // Directories appear as symlinks in /proc/#/fd; we chdir() to it
      // and see where we end up. Not efficient though.
      // Besides, we change cwd a lot in unpredictable ways. This makes
      // core dumps hard to find, if they are generated at all.
      s = "directory ";
      if(chdir(path) >= 0) {
          char buf[512];
          if(getcwd(buf, sizeof(buf)) >= 0) {
            s.append(buf);
            break;
          }
      }
      s.append("(unknown)");
      break;

    default:
      switch(sb.st_mode & S_IFMT) {
      case S_IFIFO:           // fifo or anonymous pipe
          n = "pipe"; break;
      case S_IFDIR:           // this shouldn't happen
          n = "directory"; break;
      case S_IFREG:
          n = "file"; break;
      case S_IFSOCK:
          n = "unix domain socket"; break;
      case S_IFDOOR:
          n = "door"; break;
      default:
          n = "unknown"; break;
      }
      s.sprintf("%s, dev %u:%u inode %u", n, (unsigned)major(sb.st_dev),
              (unsigned)minor(sb.st_dev), (unsigned)sb.st_ino);
      break;
    }
    fd_files->add(new Fileinfo(fdnum, s));
}
#endif // SOLARIS

// return TRUE if /proc/PID/fd could be read, FALSE otherwise
// store fileinfo, and also socket inodes separately
bool Procinfo::read_fds()
{
    char path[80], *p;
    sprintf(path, "%s/%d/fd", procdir, pid);

    DIR *d = opendir(path);
    if(!d) return FALSE;

    if(!fd_files)
      fd_files = new Svec<Fileinfo*>(8);
    fd_files->clear();

#ifdef LINUX
    if(!sock_inodes)
      sock_inodes = new Svec<SockInode>(4);
    sock_inodes->clear();
#endif

    p = path + strlen(path) + 1;
    p[-1] = '/';

    struct dirent *e;
    while((e = readdir(d)) != 0) {
      if(e->d_name[0] == '.')
          continue;           // skip . and ..
      strcpy(p, e->d_name);
      int fdnum = atoi(p);
      read_fd(fdnum, path);
    }
    closedir(d);
    return TRUE;
}

#ifdef LINUX

bool Procinfo::read_socket_list(Sockinfo::proto_t proto, char *pseudofile)
{
    char path[80];
    strcpy(path, procdir);
    strcat(path, "/net/");
    strcat(path, pseudofile);
    FILE *f = fopen(path, "r");
    if(!f) return FALSE;

    char buf[256];
    fgets(buf, sizeof(buf), f);     // skip header
    while(fgets(buf, sizeof(buf), f) != 0) {
      Sockinfo *si = new Sockinfo;
      si->proto = proto;
      unsigned local_port, rem_port, st, tr;
      sscanf(buf + 6, "%x:%x %x:%x %x %x:%x %x:%x %x %d %d %d",
             &si->local_addr, &local_port, &si->rem_addr, &rem_port,
             &st, &si->tx_queue, &si->rx_queue,
             &tr, &si->tm_when, &si->rexmits,
             &si->uid, &si->timeout, &si->inode);
      // fix fields that aren't sizeof(int)
      si->local_port = local_port;
      si->rem_port = rem_port;
      si->st = st;
      si->tr = tr;

      socks.insert(si->inode, si);
      if(socks.count() > socks.size() * 3)
          socks.resize(socks.count());
    }
    fclose(f);
    return TRUE;
}

bool Procinfo::read_usocket_list()
{
    char path[80];
    strcpy(path, procdir);
    strcat(path, "/net/unix");
    FILE *f = fopen(path, "r");
    if(!f) return FALSE;

    char buf[256];
    fgets(buf, sizeof(buf), f);     // skip header
    while(fgets(buf, sizeof(buf), f)) {
      if(buf[0])
          buf[strlen(buf) - 1] = '\0'; // chomp newline
      UnixSocket *us = new UnixSocket;
      unsigned q;
      unsigned type, state;
      int n;
      sscanf(buf, "%x: %x %x %x %x %x %ld %n",
             &q, &q, &q, &us->flags, &type, &state, &us->inode, &n);
      us->name = buf + n;
      us->type = type;
      us->state = state;
      usocks.insert(us->inode, us);
      if(usocks.count() > usocks.size() * 3)
          usocks.resize(usocks.count());
    }
    fclose(f);
    return TRUE;
}

void Procinfo::read_sockets()
{
    if(socks_current)
      return;

    socks.clear();
    if(!read_socket_list(Sockinfo::TCP, "tcp")
       || !read_socket_list(Sockinfo::UDP, "udp"))
      return;

    socks_current = TRUE;
}

void Procinfo::read_usockets()
{
    if(usocks_current)
      return;

    usocks.clear();
    if(!read_usocket_list())
      return;

    usocks_current = TRUE;
}

void Procinfo::invalidate_sockets()
{
    socks_current = usocks_current = FALSE;
}

// return TRUE if /proc/XX/maps could be read, FALSE otherwise
bool Procinfo::read_maps()
{
    // idea: here we could readlink /proc/XX/exe to identify the executable
    // when running 2.0.x
    char name[80];
    sprintf(name, "%s/%d/maps", procdir, pid);
    FILE *f = fopen(name, "r");
    if(!f) return FALSE;
    char line[1024];          // lines can be this long, or longer
    if(!maps)
      maps = new Svec<Mapsinfo *>;
    else
      maps->clear();
    
    while(fgets(line, sizeof(line), f)) {
      Mapsinfo *mi = new Mapsinfo;
      int n;
      unsigned int major, minor;
      sscanf(line, "%lx-%lx %4c %lx %x:%x %lu%n",
             &mi->from, &mi->to, mi->perm, &mi->offset,
             &major, &minor, &mi->inode, &n);
      mi->major = major; mi->minor = minor;
      if(line[n] != '\n') {
          int len = strlen(line);
          if(line[len - 1] == '\n')
            line[len - 1] = '\0';
          while(line[n] == ' ' && line[n]) n++;
          mi->filename = line + n;
      } else if((mi->major | mi->minor | mi->inode) == 0)
          mi->filename = "(anonymous)";
      maps->add(mi);
    }
    fclose(f);
    return TRUE;
}

// return TRUE if /proc/XX/environ could be read, FALSE otherwise
bool Procinfo::read_environ()
{
    int bs = 4096;            // good start
    if(envblock) free(envblock);
    envblock = (char *)malloc(bs + 1);
    char path[128];
    sprintf(path, "%s/%d/environ", procdir, pid);
    int fd = open(path, O_RDONLY);
    if(fd < 0) {
      free(envblock);
      envblock = 0;
      return FALSE;
    }
    int n;
    int ofs = 0;
    while((n = read(fd, envblock + ofs, bs - ofs)) == bs - ofs) {
      ofs = bs;
      envblock = (char *)realloc(envblock, (bs += 4096) + 1);
    }
    close(fd);
    if(n < 0) {
      free(envblock);
      envblock = 0;
      return FALSE;
    }
    n += ofs;
    envblock[n] = '\0';
    if(!environ)
      environ = new Svec<NameValue>(64);
    else
      environ->clear();
    for(int i = 0; i < n;) {
      char *p = strchr(envblock + i, '=');
      if(p)
          *p++ = '\0';
      else  // degenerate variable: treat as name with empty value
          p = envblock + i + strlen(envblock + i);
      make_printable(envblock + i);
      make_printable(p);
      environ->add(NameValue(envblock + i, p));
      i = p - envblock + strlen(p) + 1;
    }
    return TRUE;
}

#endif // LINUX

#ifdef SOLARIS

// return TRUE if the process environment could be read, FALSE otherwise
bool Procinfo::read_environ()
{
    int fd;
    char file[128];
    sprintf(file, "/proc/%d/as", pid);
    if( (fd = open(file, O_RDONLY)) < 0)
       return FALSE;

    // Just read the first 8K from the environment. Adaptive code here is
    // possible, but not really worth the effort.
    int bs = 8192;
    if(envblock) free(envblock);
    envblock = (char *)malloc(bs);
    if(pread(fd, envblock, bs, env_ofs) < 0) {
       free(envblock);
       envblock = 0;
       return FALSE;
    }
    close(fd);
    envblock[bs - 1] = '\0';

    if(!environ)
       environ = new Svec<NameValue>(64);
    else
       environ->clear();

    for(int i = 0; i * (int)sizeof(char *) < bs && ((char **)envblock)[i];
       i++) {
       int b = ((char **)envblock)[i] - (char *)env_ofs;
       if(b < 0 || b >= bs)
           continue;           // outside retrieved memory block
       char *val = strchr(envblock + b, '=');
       if(val)
           *val++ = '\0';
       else
           val = (char *)"";  // degenerate: treat as name with empty value
       make_printable(envblock + b);
       make_printable(val);
       environ->add(NameValue(envblock + b, val));
    }
    return TRUE;
}

// return TRUE if /proc/XX/map could be read, FALSE otherwise
bool Procinfo::read_maps()
{
    char name[128];
    sprintf(name, "%s/%d/map", procdir, pid);
    FILE *f = fopen(name, "r");
    if(!f) return FALSE;
    if(!maps)
      maps = new Svec<Mapsinfo *>;
    else
      maps->clear();

    prmap_t pm;
    while(fread(&pm, sizeof(pm), 1, f) == 1) {
      Mapsinfo *mi = new Mapsinfo;
      mi->from = pm.pr_vaddr;
      mi->to = pm.pr_vaddr + pm.pr_size;
      mi->offset = pm.pr_offset;
      mi->perm[0] = pm.pr_mflags & MA_READ   ? 'r' : '-';
      mi->perm[1] = pm.pr_mflags & MA_WRITE  ? 'w' : '-';
      mi->perm[2] = pm.pr_mflags & MA_EXEC   ? 'x' : '-';
      mi->perm[3] = pm.pr_mflags & MA_SHARED ? 's' : 'p';

      if(pm.pr_mapname[0]) {
          // To find device/inode, stat the file in /proc/#/object:
          char obj[128];
          sprintf(obj, "%s/%d/object/%s", procdir, pid, pm.pr_mapname);
          struct stat sb;
          if(lstat(obj, &sb) < 0) {
            delete mi;
            continue;
          }
          mi->major = major(sb.st_dev);
          mi->minor = minor(sb.st_dev);
          mi->inode = sb.st_ino;
          if(strcmp(pm.pr_mapname, "a.out") == 0)
            mi->filename = "(executable)";
      } else {
          mi->major = mi->minor = mi->inode = 0;
          mi->filename = "(anonymous)";
      }

      maps->add(mi);
    }
    fclose(f);
    return TRUE;
}

#endif // SOLARIS

Category::~Category()
{};

int Category::compare(Procinfo *a, Procinfo *b)
{
#if QT_VERSION < 200
    return strcmp(string(a), string(b));
#else
    return string(a).compare(string(b));
#endif
}

Cat_int::Cat_int(const char *heading, const char *explain,
             int w, int Procinfo::*member)
       : Category(heading, explain), int_member(member), field_width(w)
{}

QString Cat_int::string(Procinfo *p)
{
    QString s;
    s.setNum(p->*int_member); 
    return s;
}

int Cat_int::compare(Procinfo *a, Procinfo *b)
{
    // qsort() only cares about the sign of the number returned by the
    // comparison function; only a subtraction is necessary
    return a->*int_member - b->*int_member;
}

Cat_uintl::Cat_uintl(const char *heading, const char *explain, int w,
                 unsigned long Procinfo::*member)
        : Category(heading, explain), uintl_member(member), field_width(w)
{}

QString Cat_uintl::string(Procinfo *p)
{
    QString s;
    s.setNum(p->*uintl_member);     
    return s;
}

int Cat_uintl::compare(Procinfo *a, Procinfo *b)
{
    int bu = b->*uintl_member, au = a->*uintl_member;
    return bu >= au ? (bu == au ? 0 : 1) : -1;
}

Cat_hex::Cat_hex(const char *heading, const char *explain, int w,
             unsigned long Procinfo::*member)
        : Cat_uintl(heading, explain, w, member)
{}

QString Cat_hex::string(Procinfo *p)
{
    QString s;
    s.sprintf("%8lx", p->*uintl_member);
    return s;
}

Cat_swap::Cat_swap(const char *heading, const char *explain)
        : Category(heading, explain)
{}

QString Cat_swap::string(Procinfo *p)
{
    QString s;
    // It can actually happen that size < resident (XSun under Solaris 2.6)
    s.setNum(p->size > p->resident ? p->size - p->resident : 0);
    return s;
}

int Cat_swap::compare(Procinfo *a, Procinfo *b)
{
    return (b->size - b->resident) - (a->size - a->resident);
}

Cat_string::Cat_string(const char *heading, const char *explain,
                   QString Procinfo::*member)
        : Category(heading, explain), str_member(member)
{}

QString Cat_string::string(Procinfo *p)
{
    return p->*str_member;
}

Cat_user::Cat_user(const char *heading, const char *explain)
        : Cat_string(heading, explain)
{}

QString Cat_user::string(Procinfo *p)
{
#ifdef MOSIX
    if(p->isremote)
      return "-mosix-";
#endif
    if(p->uid == p->euid)
      return Uidstr::userName(p->uid);
    else {
      QString s = Uidstr::userName(p->uid);
#if QT_VERSION < 200
      s.detach();
#endif
      s.append(p->euid == 0 ? "*" : "+");
      return s;
    }
}

Cat_group::Cat_group(const char *heading, const char *explain)
        : Cat_string(heading, explain)
{}

QString Cat_group::string(Procinfo *p)
{
    if(p->gid == p->egid)
      return Uidstr::groupName(p->gid);
    else {
      QString s = Uidstr::groupName(p->gid);
#if QT_VERSION < 200
      s.detach();
#endif
      s.append("*");
      return s;
    }
}

Cat_wchan::Cat_wchan(const char *heading, const char *explain)
        : Cat_string(heading, explain)
{}

QString Cat_wchan::string(Procinfo *p)
{
    return Wchan::name(p->wchan);
}

Cat_cmdline::Cat_cmdline(const char *heading, const char *explain)
            : Cat_string(heading, explain)
{}

QString Cat_cmdline::string(Procinfo *p)
{
    if(p->cmdline.isEmpty()) {
      QString s("(");
      s.append(p->comm);
      s.append(")");
      return s;
    } else {
      if(Qps::show_cmd_path)
          return p->cmdline;
      else {
          QString s(p->cmdline);
#if QT_VERSION < 200
          s.detach();
#endif
          int i = s.find(' ');
          if(i < 0)
            i = s.length();
          if(i > 0) {
            i = s.findRev('/', i - 1);
            if(i >= 0)
                s.remove(0, i + 1);
          }
          return s;
      }
    }
}

Cat_dir::Cat_dir(const char *heading, const char *explain, const char *dirname,
             QString Procinfo::*member)
    : Cat_string(heading, explain),
      dir(dirname),
      cache(member)
{}

QString Cat_dir::string(Procinfo *p)
{
    if((p->*cache).isNull()) {
      char path[128], buf[512];
      sprintf(path, "%s/%d/%s", procdir, p->pid, dir);

#ifdef LINUX
      int n = readlink(path, buf, sizeof(buf) - 1);
      if(n < 0) {
          // Either a kernel process, or access denied.
          // A hyphen is visually least disturbing here.
          p->*cache = "-";
          return p->*cache;
      } else if(buf[0] != '[') {
           // linux >= 2.1.x: path name directly in link
          buf[n] = '\0';
          p->*cache = buf;
          return p->*cache;
      }
#endif

      // Either a Linux 2.0 link in [device]:inode form, or a Solaris link.
      // To resolve it, we just chdir() to it and see where we end up.
      // Perhaps we should change back later?
      if(chdir(path) < 0) {
           p->*cache = "-";    // Most likely access denied
      } else {
          // getcwd() is fairly expensive, but this is cached anyway
          if(!getcwd(buf, sizeof(buf))) {
            p->*cache = "(deleted)";
          } else 
            p->*cache = buf;
      }
    }
    return p->*cache;
}

Cat_state::Cat_state(const char *heading, const char *explain)
        : Category(heading, explain)
{}

QString Cat_state::string(Procinfo *p)
{
    QString s("   ");
    s[0] = p->state;
#ifdef SOLARIS
    if(p->state == 'Z')
      return s;
#endif
    s[1] = (p->resident == 0 && p->state != 'Z') ? 'W' : ' ';
    int ni = p->nice;
#ifdef SOLARIS
    if(!Qps::normalize_nice)
      ni -= NZERO;
#endif
    s[2] = (ni > 0) ? 'N' : ((ni < 0) ? '<' : ' ');
    return s;
}

Cat_policy::Cat_policy(const char *heading, const char *explain)
           : Category(heading, explain)
{}

QString Cat_policy::string(Procinfo *p)
{
    QString s;
#ifdef LINUX
    switch(p->get_policy()) {
    case SCHED_FIFO:
      s = "FI"; break;  // first in, first out
    case SCHED_RR:
      s = "RR"; break;  // round-robin
    case SCHED_OTHER:
      s = "TS"; break;  // time-sharing
    default:
      s = "??"; break;
    }
#endif
#ifdef SOLARIS
    s = "  ";
    s[0] = p->policy_name[0];
    s[1] = p->policy_name[1];
#endif
    return s;
}

int Cat_policy::compare(Procinfo *a, Procinfo *b)
{
#ifdef LINUX
    return b->get_policy() - a->get_policy();
#endif
#ifdef SOLARIS
    int r = b->policy_name[0] - a->policy_name[0];
    return r ? r : b->policy_name[1] - a->policy_name[1];
#endif
}

Cat_rtprio::Cat_rtprio(const char *heading, const char *explain)
        : Category(heading, explain)
{}

QString Cat_rtprio::string(Procinfo *p)
{
    QString s;
    s.setNum(p->get_rtprio());
    return s;
}

int Cat_rtprio::compare(Procinfo *a, Procinfo *b)
{
    return b->get_rtprio() - a->get_rtprio();
}

Cat_time::Cat_time(const char *heading, const char *explain)
        : Category(heading, explain)
{}

QString Cat_time::string(Procinfo *p)
{
    QString s;
    int ticks = p->utime;
    if(Qps::cumulative)
      ticks += p->cutime;
    int t = ticks / HZ;       // seconds
    if(t < 10) {
      int hundreds = ticks / (HZ / 100) % 100;
      s.sprintf("%1d.%02ds", t, hundreds);
    } else if(t < 100 * 60) {
      s.sprintf("%2d:%02d", t / 60, t % 60);
    } else if(t < 100 * 3600) {
      int h = t / 3600;
      t %= 3600;
      s.sprintf("%2d:%02dh", h, t / 60);
    } else {
      int d = t / 86400;
      t %= 86400;
      s.sprintf("%dd%dh", d, t / 3600);
    }
    return s;
}

int Cat_time::compare(Procinfo *a, Procinfo *b)
{
    int at = a->utime, bt = b->utime;
    if(Qps::cumulative) {
      at += a->cutime;
      bt += b->cutime;
    }
    return bt - at;
}

Cat_start::Cat_start(const char *heading, const char *explain)
          : Category(heading, explain)
{}

QString Cat_start::string(Procinfo *p)
{
#ifdef SOLARIS
    if(p->state == 'Z')
      return "-";       // Solaris zombies have no valid start time
#endif
    time_t start = p->boot_time + p->starttime / (unsigned)HZ;
    QString s;
    char *ct = ctime(&start);
    if(p->tv.tv_sec - start < 86400) {
      ct[16] = '\0';
      s = ct + 11;
    } else {
      ct[10] = '\0';
      s = ct + 4;
    }
    return s;
}

int Cat_start::compare(Procinfo *a, Procinfo *b)
{
    unsigned long bs = b->starttime, as = a->starttime;
    return bs >= as ? (bs == as ? 0 : 1) : -1;
}

Cat_percent::Cat_percent(const char *heading, const char *explain, int w,
                   float Procinfo::*member)
        : Category(heading, explain), float_member(member), field_width(w)
{}

QString Cat_percent::string(Procinfo *p)
{
    QString s;
    s.sprintf("%01.2f", (double)(p->*float_member));
    return s;
}

int Cat_percent::compare(Procinfo *a, Procinfo *b)
{
    float at = a->*float_member, bt = b->*float_member;
    return at < bt ? 1 : (at > bt ? -1 : 0);
}

Cat_tty::Cat_tty(const char *heading, const char *explain)
        : Cat_string(heading, explain)
{}

QString Cat_tty::string(Procinfo *p)
{
    return Ttystr::name(p->tty);
}

Proc::Proc()
{
    // Note: When adding/removing/changing the fields, the save file
    // version must be increased!
    allcats.set(F_PID, new Cat_int("PID", "Process ID", 6, &Procinfo::pid));
    allcats.set(F_PPID, new Cat_int("PPID", "Parent process ID", 6,
                            &Procinfo::ppid));
    allcats.set(F_PGID, new Cat_int("PGID", "Process group ID", 6,
                            &Procinfo::pgrp));
    allcats.set(F_SID, new Cat_int("SID", "Session ID", 6,
                           &Procinfo::session));
    allcats.set(F_TTY, new Cat_tty("TTY", "Controlling tty"));
#ifdef LINUX
    allcats.set(F_TPGID, new Cat_int("TPGID", "Process group ID of tty owner",
                             6, &Procinfo::tpgid));
#endif
#ifdef MOSIX
    allcats.set(F_MIGR, new Cat_string("MIGR",
                               "Process migrated to (>node) "
                               "or from (node>)", &Procinfo::migr));
    allcats.set(F_LOCKED, new Cat_int("LOCK",
                              "Whether this process is locked to"
                              " this node", 6, &Procinfo::locked));
    allcats.set(F_NMIGS, new Cat_int("NMIGS",
                             "How many times this process"
                             " has migrated", 6, &Procinfo::nmigs));
    allcats.set(F_NOMOVE, new Cat_string("NOMOVE",
                               "Reason for why process can't move",
                               &Procinfo::cantmove));
    allcats.set(F_RPID, new Cat_int("RPID",
                               "Process ID on home node", 6,
                               &Procinfo::remotepid));
#endif

    allcats.set(F_USER, new Cat_user("USER",
                            "Owner (*=suid root, +=suid other user"
#ifdef MOSIX
                             " -mosix-=immigrated from other node"
#endif
                             ")"));
    allcats.set(F_GROUP, new Cat_group("GROUP", "Group name (*=sgid other)"));
    allcats.set(F_UID, new Cat_int("UID", "Real user ID", 6, &Procinfo::uid));
    allcats.set(F_EUID, new Cat_int("EUID", "Effective user ID", 6,
                            &Procinfo::euid));
#ifdef LINUX
    allcats.set(F_SUID, new Cat_int("SUID", "Saved user ID (Posix)", 6,
                            &Procinfo::suid));
    allcats.set(F_FSUID, new Cat_int("FSUID", "File system user ID", 6,
                             &Procinfo::fsuid));
#endif
    allcats.set(F_GID, new Cat_int("GID", "Real group ID", 6, &Procinfo::gid));
    allcats.set(F_EGID, new Cat_int("EGID", "Effective group ID", 6,
                            &Procinfo::egid));
#ifdef LINUX
    allcats.set(F_SGID, new Cat_int("SGID", "Saved group ID (Posix)", 6,
                            &Procinfo::sgid));
    allcats.set(F_FSGID, new Cat_int("FSGID", "File system group ID", 6,
                             &Procinfo::fsgid));
#endif
    allcats.set(F_PRI, new Cat_int("PRI", "Dynamic priority", 4,
                           &Procinfo::priority));
    allcats.set(F_NICE, new Cat_int("NICE",
                        "Scheduling favour (higher -> less cpu time)",
                            4, &Procinfo::nice));
    allcats.set(F_PLCY, new Cat_policy("PLCY",
                               "Scheduling policy"));
    allcats.set(F_RPRI, new Cat_rtprio("RPRI",
                          "Realtime priority (0-99, more is better)"));
#ifdef SOLARIS
    allcats.set(F_NLWP, new Cat_int("NLWP", "Number of threads in process",
                            5, &Procinfo::nthreads));
#endif
    allcats.set(F_MAJFLT, new Cat_uintl("MAJFLT",
                              "Number of major faults (disk access)",
                              8, &Procinfo::majflt));
    allcats.set(F_MINFLT, new Cat_uintl("MINFLT",
                             "Number of minor faults (no disk access)",
                              8, &Procinfo::minflt));
#ifdef LINUX
    allcats.set(F_TRS, new Cat_uintl("TRS", "Text resident set size in Kbytes",
                             8, &Procinfo::trs));
    allcats.set(F_DRS, new Cat_uintl("DRS", "Data resident set size in Kbytes",
                             8, &Procinfo::drs));
#endif
    allcats.set(F_SIZE, new Cat_uintl("SIZE",
                             "Virtual image size of process in Kbytes",
                              8, &Procinfo::size));
    allcats.set(F_SWAP, new Cat_swap("SWAP", "Kbytes on swap device"));
    allcats.set(F_RSS, new Cat_uintl("RSS",
                             "Resident set size; Kbytes of program "
                             "in memory", 8, &Procinfo::resident));
#ifdef LINUX
    allcats.set(F_SHARE, new Cat_uintl("SHARE", "Shared memory in Kbytes",
                               8, &Procinfo::share));
    allcats.set(F_DT, new Cat_uintl("DT",
                            "Number of dirty (non-written) pages",
                            7, &Procinfo::dt));
#endif
    allcats.set(F_STAT, new Cat_state("STAT", "State of the process"));
    allcats.set(F_FLAGS, new Cat_hex("FLAGS", "Process flags (hex)", 9,
                             &Procinfo::flags));
    allcats.set(F_WCHAN, new Cat_wchan("WCHAN",
                         "Kernel function where process is sleeping"));
    allcats.set(F_WCPU, new Cat_percent("%WCPU",
                           "Weighted percentage of CPU (30 s average)",
                              6, &Procinfo::wcpu));
    allcats.set(F_CPU, new Cat_percent("%CPU",
                            "Percentage of CPU used since last update",
                               6, &Procinfo::pcpu));
    allcats.set(F_MEM, new Cat_percent("%MEM",
                          "Percentage of memory used (RSS/total mem)",
                               6, &Procinfo::pmem));
    allcats.set(F_START, new Cat_start("START", "Time process started"));
    allcats.set(F_TIME, new Cat_time("TIME",
                             "Total CPU time used since start"));
    allcats.set(F_CPUNUM, new Cat_int("CPU", "CPU the process is executing on",
                              3, &Procinfo::which_cpu));
    allcats.set(F_COMM, new Cat_string("COMM",
                               "Command that started the process",
                               &Procinfo::comm));
    allcats.set(F_CWD, new Cat_dir("CWD", "Current working directory",
                           "cwd", &Procinfo::cwd));
    allcats.set(F_ROOT, new Cat_dir("ROOT", "Root directory of process",
                            "root", &Procinfo::root));
    allcats.set(F_CMDLINE, new Cat_cmdline("CMDLINE",
                             "Command line that started the process"));

    for(int i = 0; i < allcats.size(); i++)
      allcats[i]->index = i;

    Procinfo::init_static();
}

void Proc::newproc(Procinfo *p)
{
    Procinfo *oldp = procs[p->pid];
    if(oldp) {
#ifdef LINUX
      // calculate pcpu and wcpu from previous procinfo
      int dt = (p->tv.tv_usec - oldp->tv.tv_usec) / (1000000 / HZ)
               + (p->tv.tv_sec - oldp->tv.tv_sec) * HZ;
      int dcpu = p->utime - oldp->utime;
      p->pcpu = 100.0 * dcpu / dt;
      if(p->pcpu > 99.99) p->pcpu = 99.99;
#endif

      const float a = Procview::avg_factor;
      p->wcpu = a * oldp->wcpu + (1 - a) * p->pcpu;

      // propagate some fields to new incarnation
      p->selected = oldp->selected;
      p->details = oldp->details;
      p->hidekids = oldp->hidekids;
      oldp->details = 0;
      if(p->details)
          p->details->set_procinfo(p);

#ifdef LINUX
      if(Procinfo::num_cpus > 1) {
          // SMP: see which processor was used the most
          int best_cpu = -1;
          unsigned long most = 0;
          for(unsigned cpu = 0; cpu < Procinfo::num_cpus; cpu++) {
            unsigned long delta =
                p->per_cpu_times[cpu] - oldp->per_cpu_times[cpu];
            if(delta > most) {
                most = delta;
                best_cpu = cpu;
            }
            // if no cpu time has been spent, use previous value
            p->which_cpu = (best_cpu >= 0) ? best_cpu : oldp->which_cpu;
          }
      }
#endif
      oldp->deref();
    } else {
      // New process
#ifdef LINUX
      // %cpu first time = (cpu time since start) / (time since start)
      int jiffies_since_boot = p->tv.tv_usec / (1000000 / HZ)
          + (p->tv.tv_sec - p->boot_time) * HZ;
      int dt = jiffies_since_boot - p->starttime;
      int dcpu = p->utime;
      p->pcpu = 100.0 * dcpu / dt;
      if(dt == 0 || p->pcpu > 99.99 || p->pcpu < 0)
          p->pcpu = 0.0;
      p->selected = FALSE;
#endif
      p->wcpu = p->pcpu;      // just a start

#ifdef LINUX
      if(Procinfo::num_cpus > 1) {
          // first tick: count times from 0
          unsigned long most = 0;
          for(unsigned cpu = 0; cpu < Procinfo::num_cpus; cpu++) {
            unsigned long t = p->per_cpu_times[cpu];
            if(t > most) {
                most = t;
                p->which_cpu = cpu;
            }
          }
      }
#endif
    }
    procs.replace(p->pid, p);
    if(procs.count() > procs.size())
      procs.resize(procs.count() * 2 - 1);
}

// update the process list
void Proc::refresh()
{
    static bool current_gen;
    current_gen = !current_gen;
    Procinfo::read_common();
    
    DIR *d = opendir(procdir);
    struct dirent *e;
    while((e = readdir(d)) != 0) {
      if(e->d_name[0] >= '0' && e->d_name[0] <= '9') {
          Procinfo *pi = new Procinfo(atoi(e->d_name));
          if(pi->pid == -1)
            delete pi;        // already gone
          else {
            pi->generation = current_gen;
            newproc(pi);
          }
      }
    }
    closedir(d);

#ifdef MOSIX
    char path[256];
    strcpy(path, procdir);
    strcat(path, "/mosix/remote");
    d = opendir(path);
    if(d) {
      while((e = readdir(d)) != 0) {
          if(e->d_name[0] >= '0' && e->d_name[0] <= '9') {
            Procinfo *pi = new Procinfo(atoi(e->d_name));
            if(pi->pid == -1)
                delete pi;          // already gone
            else {
                pi->generation = current_gen;
                newproc(pi);
            }
          }
      }
      closedir(d);
    }
#endif // MOSIX

    // remove Procinfos of nonexisting processes
    for(QIntDictIterator<Procinfo> it(procs); it.current();) {
      Procinfo *p = it.current();
      if(p->generation != current_gen) {
          procs.remove(p->pid);
          p->deref();
      } else
          ++it;
    }
}

int Procview::user_fields[] = {F_PID, F_TTY, F_USER, F_NICE,
#ifdef SOLARIS
                         F_NLWP,
#endif
                         F_SIZE, F_RSS,
                         F_STAT, F_CPU, F_START, F_TIME,
                         F_CMDLINE, F_END};

#ifdef MOSIX
int Procview::user_fields_mosix[] = {F_PID, F_TTY, F_USER, F_NICE,
                             F_MIGR, F_NMIGS, 
                             F_SIZE, F_RSS,
                             F_STAT, F_CPU, F_START, F_TIME,
                             F_CMDLINE, F_END};
#endif

int Procview::jobs_fields[] = {F_PID, F_PPID, F_PGID, F_SID, 
                         F_TTY,
#ifdef LINUX
                         F_TPGID,
#endif
                         F_STAT, F_UID, F_TIME, F_CMDLINE, F_END};
#ifdef MOSIX
int Procview::jobs_fields_mosix[] = {F_PID, F_PPID, F_PGID, F_SID, 
                             F_MIGR, F_NMIGS, F_LOCKED, F_NOMOVE,
                             F_TTY,
                             F_TPGID,
                             F_STAT, F_UID, F_TIME, F_CMDLINE, F_END};
#endif

int Procview::mem_fields[] = {F_PID, 
                        F_TTY, F_MAJFLT, F_MINFLT,
#ifdef LINUX
                        F_TRS, F_DRS,
#endif
                        F_SIZE, F_SWAP, F_RSS,
#ifdef LINUX
                        F_SHARE, F_DT,
#endif
                        F_CMDLINE, 
                        F_END};
#ifdef MOSIX
int Procview::mem_fields_mosix[] = {F_PID, 
                            F_MIGR,
                            F_TTY, F_MAJFLT, F_MINFLT,
                            F_TRS, F_DRS,
                            F_SIZE, F_SWAP, F_RSS,
                            F_SHARE, F_DT,
                            F_CMDLINE, 
                            F_END};
#endif

float Procview::avg_factor = 1.0;

Procview::Procview(Proc *p)
        : proc(p)
{
    sortcat = p->allcats[F_WCPU];
    reversed = FALSE;
    viewproc = ALL;
    viewfields = USER;
    treeview = FALSE;
    set_fields();
}

// read new process info
void Procview::refresh()
{
    for(int i = 0; i < old_procs.size(); i++)
      old_procs[i]->deref();
    old_procs = procs;
    procs.clear();
    proc->refresh();
    rebuild();
}

bool Procview::accept_proc(Procinfo *p)
{
    static int my_uid = getuid();
    return viewproc == ALL
      || viewproc == OWNED && p->uid == my_uid
      || viewproc == NROOT && p->uid != 0
      || viewproc == RUNNING && strchr("ORDW", p->state) != 0
#ifdef MOSIX      
      || viewproc == RUNNING && p->where > 0
#endif
      ;
}

void Procview::build_tree()
{
    if(root_procs.size() > 0) {
      Procinfo *p;
      for(QIntDictIterator<Procinfo> it(proc->procs); (p = it.current());
          ++it)
          if(p->children)
            p->children->clear();
      root_procs.clear();
    }
    Procinfo *p;
    for(QIntDictIterator<Procinfo> it(proc->procs); (p = it.current()); ++it) {
      if(accept_proc(p)) {
          Procinfo *parent = 0;
          if(p->ppid && (parent = proc->procs[p->ppid])
             && accept_proc(parent)) {
            if(!parent->children)
                parent->children = new Svec<Procinfo *>(4);
            parent->children->add(p);
          } else
            root_procs.add(p);
      } else
          p->selected = FALSE;
    }
}

// re-sort the process info
void Procview::rebuild()
{
    for(int i = 0; i < procs.size(); i++)
      procs[i]->deref();
    procs.clear();
    if(treeview) {
      build_tree();
      parent_rows.clear();
      linearize_tree(&root_procs, 0, -1);
    } else {
      for(QIntDictIterator<Procinfo> it(proc->procs); it.current(); ++it) {
          Procinfo *p = it.current();
          if(accept_proc(p))
            procs.add(p->ref());
          else
            p->selected = FALSE;
      }
      static_sortcat = sortcat;
      procs.sort(reversed ? compare_backwards : compare);
    }
}

void Procview::linearize_tree(Svec<Procinfo *> *ps, int level, int prow)
{
    static_sortcat = sortcat;
    ps->sort(reversed ? compare_backwards : compare);
    for(int i = 0; i < ps->size(); i++) {
      Procinfo *p = (*ps)[i];
      p->level = level;
      p->lastchild = FALSE;
      procs.add(p->ref());
      parent_rows.add(prow);
      if(p->children && !p->hidekids)
          linearize_tree(p->children, level + 1, procs.size() - 1);
    }
    if(ps->size() > 0)
      (*ps)[ps->size() - 1]->lastchild = TRUE;
}

void Procview::set_fields_list(int fields[])
{
    cats.clear();
    for(int i = 0; fields[i] != F_END; i++)
      cats.add(proc->allcats[fields[i]]);
}

void Procview::set_fields()
{
    switch(viewfields) {
    case USER:
#ifdef MOSIX
      if(Procinfo::mosix_running) {
          set_fields_list(user_fields_mosix);
          break;
      }
#endif
      set_fields_list(user_fields);
      break;
    case JOBS:
#ifdef MOSIX
      if(Procinfo::mosix_running) {
          set_fields_list(jobs_fields_mosix);
          break;
      } 
#endif
      set_fields_list(jobs_fields);
      break;
    case MEM:
#ifdef MOSIX
      if(Procinfo::mosix_running) {
          set_fields_list(mem_fields_mosix);
          break;
      } 
#endif
      set_fields_list(mem_fields);
      break;
    case CUSTOM:
      ;
    }
}

// return the column number of a field, or -1 if not displayed
int Procview::findCol(int field)
{
    for(int i = 0; i < cats.size(); i++)
      if(cats[i] == proc->allcats[field])
          return i;
    return -1;
}

// add a category (last)
void Procview::add_cat(Category *c)
{
    cats.add(c);
}

void Procview::remove_cat(int index)
{
    cats.remove(index);
}

Category *Procview::static_sortcat = 0;

int Procview::compare(Procinfo *const *a, Procinfo *const *b)
{
    int r = static_sortcat->compare(*a, *b);
    return (r == 0) ? ((*a)->pid > (*b)->pid ? 1 : -1) : r;
}

int Procview::compare_backwards(Procinfo *const *a, Procinfo *const *b)
{
    int r = static_sortcat->compare(*b, *a);
    return (r == 0) ? ((*b)->pid > (*a)->pid ? 1 : -1) : r;
}


Generated by  Doxygen 1.6.0   Back to index