340 lines
9.9 KiB
C
340 lines
9.9 KiB
C
/**************************************************************************
|
|
* *
|
|
* Copyright (C) 1997, Silicon Graphics, Inc. *
|
|
* *
|
|
* These coded instructions, statements, and computer programs contain *
|
|
* unpublished proprietary information of Silicon Graphics, Inc., and *
|
|
* are protected by Federal copyright law. They may not be disclosed *
|
|
* to third parties or copied or duplicated in any form, in whole or *
|
|
* in part, without the prior written consent of Silicon Graphics, Inc. *
|
|
* *
|
|
**************************************************************************/
|
|
|
|
/*
|
|
* KID -> process name handling. When a KID is referenced in an
|
|
* event the server is obligated to send a "task name" event ahead
|
|
* that specifies the "name" of the associated process. This
|
|
* operation happens a lot so we keep a per-client cache of which
|
|
* PIDs have been processed (i.e. the client sent a task name
|
|
* mapping event) and also keep a per-thread cache of KID->name
|
|
* mappings (the latter to avoid having each client query the OS
|
|
* to find out the process name). Keeping state on a per-client
|
|
* basis also simplifies cleanup when a client goes away; the
|
|
* state is just reclaimed as part of the client exit procedure.
|
|
*/
|
|
#include "rtmond.h"
|
|
|
|
#include <sys/syssgi.h>
|
|
#include <fcntl.h>
|
|
#include <paths.h>
|
|
#include <sys/procfs.h>
|
|
|
|
/*
|
|
* XXX collect stats on how well the hashing/cacheing works.
|
|
*/
|
|
|
|
/*
|
|
* Process names are at most PRCOMSIZ long.
|
|
* Typically this is 32 bytes.
|
|
*/
|
|
#define NAMELEN ((PRCOMSIZ+1)&~1)
|
|
|
|
struct kidentry {
|
|
kidentry_t* next; /* linked list of entries */
|
|
int64_t kid; /* kthread id */
|
|
pid_t pid; /* PID */
|
|
char name[NAMELEN]; /* process tag/name */
|
|
};
|
|
|
|
#define KIDCACHESIZE 4093 /* prime; close to 4K */
|
|
#define HASH(kid) (((kid) + 32768) % KIDCACHESIZE)
|
|
|
|
#define NKIDS (sizeof (((kidblock_t*)0)->kids) / sizeof (int64_t))
|
|
|
|
void
|
|
kid_init(daemon_info_t* dp)
|
|
{
|
|
dp->kidcache = (kidentry_t**) malloc(KIDCACHESIZE * sizeof (kidentry_t*));
|
|
memset(dp->kidcache, 0, KIDCACHESIZE * sizeof (kidentry_t*));
|
|
dp->lastkid = NULL;
|
|
}
|
|
|
|
void
|
|
kid_flush(daemon_info_t* dp)
|
|
{
|
|
kidentry_t* ke;
|
|
int i;
|
|
|
|
IFTRACE(KID)(dp, "Flush kid cache");
|
|
for (i = 0; i < KIDCACHESIZE; i++)
|
|
while (ke = dp->kidcache[i]) {
|
|
dp->kidcache[i] = ke->next;
|
|
IFTRACE(KID)(dp, "Flush KID entry: %lld/%s",
|
|
ke->kid, ke->name);
|
|
free(ke);
|
|
}
|
|
dp->lastkid = NULL;
|
|
}
|
|
|
|
/*
|
|
* Purge any cached entry for the specified KID.
|
|
*/
|
|
void
|
|
kid_purge_kid(daemon_info_t* dp, int64_t kid)
|
|
{
|
|
kidentry_t* ke;
|
|
kidentry_t** kep;
|
|
|
|
IFTRACE(KID)(dp, "Purge state for KID %lld", kid);
|
|
for (kep = &dp->kidcache[HASH(kid)]; ke = *kep; kep = &ke->next)
|
|
if (ke->kid == kid) {
|
|
client_t* cp;
|
|
*kep = ke->next;
|
|
free(ke);
|
|
/* purge client references to kid */
|
|
for (cp = (client_t*) dp->clients; cp; cp = cp->next) {
|
|
kidblock_t* pb;
|
|
for (pb = &cp->kids; pb; pb = pb->next) {
|
|
int i;
|
|
for (i = 0; i < NKIDS; i++)
|
|
if (pb->kids[i] == kid) {
|
|
IFTRACE(KID)(dp, "Purge KID %lld for client %s:%u",
|
|
kid, cp->host, cp->port);
|
|
pb->kids[i] = 0;
|
|
if (cp->lastkid == kid)
|
|
cp->lastkid = (int64_t) -1;
|
|
goto next;
|
|
}
|
|
}
|
|
next:
|
|
;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check whether the specified KID (encountered in the event
|
|
* stream) has been seen before. If this is the first time
|
|
* any client has seen it, send a "task name" protocol message
|
|
* to the client and record the KID in the client's state
|
|
* block so future events won't cause another message to be
|
|
* transmitted.
|
|
*/
|
|
void
|
|
kid_check_kid(daemon_info_t* dp, int64_t kid, int64_t tv)
|
|
{
|
|
client_t* cp = (client_t*) dp->clients;
|
|
|
|
#ifdef notdef
|
|
IFTRACE(KID)(dp, "Check kid %lld", kid);
|
|
#endif
|
|
while (cp) {
|
|
if (cp->lastkid != kid) {
|
|
kidblock_t* pb;
|
|
int64_t* fp = NULL;
|
|
|
|
for (pb = &cp->kids; pb; pb = pb->next) {
|
|
/* XXX eliminate linear search */
|
|
int i;
|
|
for (i = 0; i < NKIDS; i++) {
|
|
if (pb->kids[i] == kid)
|
|
goto hit;
|
|
if (pb->kids[i] == 0)
|
|
fp = &pb->kids[i];
|
|
}
|
|
}
|
|
/*
|
|
* We could check the return value here and blow away
|
|
* the client immediately, but since we know we're called
|
|
* as a result of processing an event, it's likely that
|
|
* event will get dispatched to the client and the subsequent
|
|
* write will cause the client to be collected in the
|
|
* the normal/main tstamp processing loop.
|
|
*/
|
|
IFTRACE(KID)(dp, "Send %s:%u task name", cp->host, cp->port);
|
|
(*cp->proto->writeTaskName)(dp, cp, kid, tv);
|
|
/* record KID in client's state */
|
|
if (!fp) {
|
|
/*
|
|
* No space; add another block.
|
|
*/
|
|
IFTRACE(KID)(dp, "New kid cache block for client %s:%u",
|
|
cp->host, cp->port);
|
|
pb = (kidblock_t*) malloc(sizeof (*pb));
|
|
assert(pb != NULL); /* XXX */
|
|
memset(pb, 0, sizeof (*pb));
|
|
pb->next = cp->kids.next;
|
|
cp->kids.next = pb;
|
|
fp = &pb->kids[0];
|
|
}
|
|
*fp = kid;
|
|
hit:
|
|
cp->lastkid = kid;
|
|
}
|
|
cp = cp->next;
|
|
}
|
|
}
|
|
|
|
static void
|
|
kid_name(int64_t kid, char* name)
|
|
{
|
|
sprintf(name, "kthread%lld" , kid);
|
|
}
|
|
|
|
/*
|
|
* Map a KID to a process/command name. If the KID is
|
|
* associated with a kernel thread then we ask the OS
|
|
* for a name. Otherwise we fetch the information by
|
|
* going through /dev/proc (ouch).
|
|
*/
|
|
static void
|
|
kid_get_command_name(daemon_info_t *dp, int64_t kid, pid_t *pid,
|
|
char *name)
|
|
{
|
|
int len = NAMELEN;
|
|
*pid = syssgi(SGI_GET_CONTEXT_NAME, &kid, name, &len);
|
|
if (strlen(name) > len || len == 0)
|
|
name[len] = '\0';
|
|
IFTRACE(KID)(dp, "Kid %lld got pid %d and name %s len %d", kid, *pid, name, len);
|
|
if (*pid == -1) {
|
|
IFTRACE(KID)(dp, "Kid %lld not found errno is %d", kid, errno);
|
|
kid_name(kid, name);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Lookup the specified KID. We check the cache
|
|
* first. If that fails we lookup the entry and
|
|
* try to record the info in the cache. We keep
|
|
* a pointer to the last lookup as an optimization
|
|
* for multiple clients interested in the same
|
|
* processes and because processes run for a time
|
|
* quantum it's likely per-process events will
|
|
* arrive clustered.
|
|
*/
|
|
void
|
|
kid_lookup_kid(daemon_info_t* dp, int64_t kid, pid_t *pid,
|
|
char* buf, size_t *len)
|
|
{
|
|
kidentry_t* ke = dp->lastkid;
|
|
IFTRACE(KID)(dp, "Lookup kid %lld", kid);
|
|
if (!ke || ke->kid != kid) {
|
|
int h = HASH(kid);
|
|
for (ke = dp->kidcache[h]; ke && ke->kid != kid; ke = ke->next)
|
|
;
|
|
if (!ke) { /* not in cache */
|
|
ke = (kidentry_t*) malloc(sizeof (*ke));
|
|
IFTRACE(KID)(dp, "kid %lld is new entry", kid);
|
|
if (!ke) {
|
|
/* no space for new entry, return info directly */
|
|
IFTRACE(KID)(dp, "No space to cache entry for %lld", kid);
|
|
kid_get_command_name(dp, kid, pid, buf);
|
|
return;
|
|
}
|
|
ke->kid = kid;
|
|
kid_get_command_name(dp, kid, &ke->pid, ke->name);
|
|
ke->next = dp->kidcache[h]; /* NB: place at front */
|
|
dp->kidcache[h] = ke;
|
|
IFTRACE(KID)(dp,
|
|
"New kid mapping: %lld(pid-%d) -> %s", ke->kid, ke->pid, ke->name);
|
|
}
|
|
dp->lastkid = ke; /* set cached KID */
|
|
}
|
|
strncpy(buf, ke->name, NAMELEN);
|
|
*pid = ke->pid;
|
|
IFTRACE(KID)(dp, "Kid mapping: %lld(%d) -> %s", ke->kid, ke->pid, ke->name);
|
|
}
|
|
|
|
/*
|
|
* Rename the specified KID. If no entry existed, then
|
|
* create one. This interface is used to process kernel
|
|
* events that inform us when a process does an exec.
|
|
*/
|
|
void
|
|
kid_rename_kid(daemon_info_t* dp, int64_t kid, const char* name, int64_t tv, pid_t pid)
|
|
{
|
|
kidentry_t* ke = dp->lastkid;
|
|
int h;
|
|
|
|
IFTRACE(KID)(dp, "Rename kid %lld(pid-%d) to %s", kid, pid, name);
|
|
if (!ke || ke->kid != kid) { /* check cache */
|
|
h = HASH(kid);
|
|
for (ke = dp->kidcache[h]; ke && ke->kid != kid; ke = ke->next)
|
|
;
|
|
}
|
|
if (!ke) { /* not found, make new entry */
|
|
pid_t pid2;
|
|
char name[NAMELEN];
|
|
int len = NAMELEN;
|
|
pid2 = syssgi(SGI_GET_CONTEXT_NAME, &kid, name, &len);
|
|
if (strlen(name) > len || len == 0)
|
|
name[len] = '\0';
|
|
ke = (kidentry_t*) malloc(sizeof (*ke));
|
|
if (!ke) {
|
|
IFTRACE(KID)(dp, "No space to add entry for %lld", kid);
|
|
return;
|
|
}
|
|
strncpy(ke->name, name, NAMELEN);
|
|
ke->kid = kid;
|
|
ke->pid = pid2;
|
|
ke->next = dp->kidcache[h]; /* NB: place at front */
|
|
dp->kidcache[h] = ke;
|
|
}
|
|
if ((int)pid > 0)
|
|
ke->pid = pid;
|
|
/*
|
|
* If the name has chaned then put the new name here.
|
|
* We do not copy if the beginning is the same as
|
|
* fork is not able to pass up the full name and
|
|
* we do not want to lose information.
|
|
*/
|
|
if (strncmp(ke->name, name, strlen(name))) {
|
|
client_t* cp = (client_t*) dp->clients;
|
|
|
|
strncpy(ke->name, name, NAMELEN);
|
|
while (cp) {
|
|
IFTRACE(KID)(dp, "Send %s:%u task name", cp->host, cp->port);
|
|
(*cp->proto->writeTaskName)(dp, cp, kid, tv);
|
|
cp = cp->next;
|
|
}
|
|
}
|
|
dp->lastkid = ke; /* set cached KID */
|
|
IFTRACE(KID)(dp, "New kid mapping: %lld(pid-%d) -> %s", ke->kid,ke->pid, name);
|
|
}
|
|
/*
|
|
* process fork events
|
|
*/
|
|
void
|
|
kid_dup_kid(daemon_info_t* dp, int64_t pkid, int64_t ckid)
|
|
{
|
|
kidentry_t* ke = dp->lastkid;
|
|
int h;
|
|
|
|
/*
|
|
* Make sure the clients have the kid->pid mappings
|
|
*/
|
|
IFTRACE(KID)(dp, "Dup kid; parent %lld, child %lld", pkid, ckid);
|
|
if (!ke || ke->kid != pkid) {
|
|
h = HASH(pkid);
|
|
for (ke = dp->kidcache[h]; ke && ke->kid != pkid; ke = ke->next)
|
|
;
|
|
}
|
|
if (ke) { /* parent located, add child */
|
|
kidentry_t* cke = (kidentry_t*) malloc(sizeof (*cke));
|
|
if (!cke) {
|
|
IFTRACE(KID)(dp, "No space to add entry for %lld", ckid);
|
|
return;
|
|
}
|
|
cke->kid = ckid;
|
|
strcpy(cke->name, ke->name);
|
|
h = HASH(ckid);
|
|
cke->next = dp->kidcache[h]; /* NB: place at front */
|
|
dp->kidcache[h] = cke;
|
|
IFTRACE(KID)(dp,
|
|
"New kid mapping: %lld -> %s", cke->kid, cke->name);
|
|
dp->lastkid = cke; /* set cached KID */
|
|
} else
|
|
IFTRACE(KID)(dp, "No entry for parent; child entry not made");
|
|
}
|