diff -urN tmp/orig/xscreensaver-5.07/driver/XScreenSaver.ad.in xscreensaver-5.07/driver/XScreenSaver.ad.in --- tmp/orig/xscreensaver-5.07/driver/XScreenSaver.ad.in 2008-08-11 05:50:05.000000000 +0100 +++ xscreensaver-5.07/driver/XScreenSaver.ad.in 2008-11-16 00:32:52.000000000 +0000 @@ -223,6 +223,7 @@ epicycle -root \n\ flow -root \n\ @GL_KLUDGE@ GL: glplanet -root \n\ +@GL_KLUDGE@ GL: glpidstat -root \n\ interference -root \n\ jigsaw -root \n\ kumppa -root \n\ diff -urN tmp/orig/xscreensaver-5.07/hacks/config/glpidstat.xml xscreensaver-5.07/hacks/config/glpidstat.xml --- tmp/orig/xscreensaver-5.07/hacks/config/glpidstat.xml 1970-01-01 01:00:00.000000000 +0100 +++ xscreensaver-5.07/hacks/config/glpidstat.xml 2008-11-13 17:35:17.000000000 +0000 @@ -0,0 +1,29 @@ + + + + + + + + + + + <_description> + +The 'pidstat' program, that is part of the systat suite, displays the state of +processes on a Linux system. This hack displays the output of pidstat +graphically so you can sit back and watch your machine. This hack +requires version 8.1.6 of pidstat (or newer). + +Written by Dr. David Alan Gilbert; 2008. + + + diff -urN tmp/orig/xscreensaver-5.07/hacks/glx/glpidstat.c xscreensaver-5.07/hacks/glx/glpidstat.c --- tmp/orig/xscreensaver-5.07/hacks/glx/glpidstat.c 1970-01-01 01:00:00.000000000 +0100 +++ xscreensaver-5.07/hacks/glx/glpidstat.c 2008-11-16 15:53:25.000000000 +0000 @@ -0,0 +1,1251 @@ +/* -*- Mode: C; tab-width: 4 -*- */ +/* glpidstat --- Display of pidstat process data using GL. */ + +/* Nice to do's: + Filter stuff for low scores and don't bother to add? + I took out context switches because you get way too many processes + Score processes that have just been swapped differently so that + we don't thrash as much + Gamma the colours so 10% cpu is noticeable + Look at intervals < a second in pidstat + + It's difficult to associate the text with a particular stripe - especially with + descenders - texfont doesn't seem to tell us about descenders + + Notes: + On the main stripe, Red is System, Green is total, user is blue (biased up) + On the IO stripe (right) Red is read (!), Green is written + + Originally the cpu usage line drawn in each stripe was + stripewidth/ncpu - that's OK on a small number of cores but really + stinks on a 16way box where the line is very thin; so now it's always + stripewidth/2 with an offset that's offset within the stripe + +*/ + +/*- + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose and without fee is hereby granted, + * provided that the above copyright notice appear in all copies and that + * both that copyright notice and this permission notice appear in + * supporting documentation. + * + * This file is provided AS IS with no warranties of any kind. The author + * shall have no liability with respect to the infringement of copyrights, + * trade secrets or any patents by this file or any part thereof. In no + * event will the author be liable for any lost revenue or profits or + * other special, indirect and consequential damages. + * + * Thanks goes to Brian Paul for making it possible and inexpensive to use + * OpenGL at home. + * + * Dr. David Alan Gilbert (2008-09-14) + * + * Revision History: + * 2008-09-14: Started by taking 'moebius' as a template + */ + +#ifdef STANDALONE +# define MODE_glpidstat +# define refresh_glpidstat 0 + +#define DEF_FONT "-*-times-bold-r-normal-*-240-*" +# define DEFAULTS "*delay: 500000 \n" \ + "*showFPS: False \n" \ + "*font: " DEF_FONT "\n" \ + +# include "xlockmore.h" /* from the xscreensaver distribution */ +#else /* !STANDALONE */ +# include "xlock.h" /* from the xlockmore distribution */ +#endif /* !STANDALONE */ + +#include +/* For FLT_MAX */ +#include +/* For XtAppAddInput */ +#include + +#ifdef MODE_glpidstat + +#include "gltrackball.h" +#include "rotator.h" +#include "texfont.h" + +#define DEF_PIDSTATCOMMANDSTRING "pidstat -h -u -r -d 1" +/* Hmm do I want to determine this from window size? */ +#define DEF_NUMSTRIPES "40" +#define DEF_SAMPLESPERSTRIP "30" +#define DEF_MAXRANGEIO "1024" +#define LINEBUFFERSIZE 1024 + +static char* pidstatcommandstring; +static unsigned int numStripes; /* That's the number of processes to show accross */ +static unsigned int samplesPerStripe; /* Number of samples vertically */ +static float maxRangeIO; /* Amount of IO to be full colour */ + +static XrmOptionDescRec opts[] = +{ + {"-pidstatcommandstring", ".pidstatcommandstring", XrmoptionSepArg, 0}, + {"-numstripes", ".numstripes", XrmoptionSepArg, 0}, + {"-samplesperstripe", ".samplesperstripe", XrmoptionSepArg, 0}, + {"-maxrangeio", ".maxrangeio", XrmoptionSepArg, 0} +}; +static argtype vars[] = +{ + {&pidstatcommandstring, "pidstatcommandstring", "Pidstatcommandstring", DEF_PIDSTATCOMMANDSTRING, t_String}, + {&numStripes, "numstripes", "Numstripes", DEF_NUMSTRIPES, t_Int}, + {&samplesPerStripe, "samplesperstripe", "Samplesperstripe", DEF_SAMPLESPERSTRIP, t_Int}, + {&maxRangeIO, "maxrangeio", "Maxrangeio", DEF_MAXRANGEIO, t_Float}, + +}; +static OptionStruct desc[] = +{ + {"-pidstatcommandstring", "The command to run pidstat - normally with the -h option"}, + {"-numstripes", "Number of vertical stripes"}, + {"-samplesperstripe", "Number of samples down"}, + {"-maxrangeio", "The amount of IO to make the colours go to full brightness"} +}; + +ENTRYPOINT ModeSpecOpt glpidstat_opts = +{sizeof opts / sizeof opts[0], opts, sizeof vars / sizeof vars[0], vars, desc}; + +#ifdef USE_MODULES +ModStruct glpidstat_description = +{"glpidstat", "init_glpidstat", "draw_glpidstat", "release_glpidstat", + "draw_glpidstat", "change_glpidstat", (char *) NULL, &glpidstat_opts, + 1000, 1, 1, 1, 4, 1.0, "", + "Shows system state using pidstat.", 0, NULL}; + +#endif + +#define sqr(A) ((A)*(A)) + +#ifndef Pi +#define Pi M_PI +#endif + +/*************************************************************************/ + +/* The type of the time reported by pidstat */ +typedef long pidstatTimeT; + +/* One sample for a process or thread - threads don't necessarily have it all right */ +typedef struct { + float user, system, guest, total; /* 0...1 from % cpu used */ + unsigned int curCPU; /* Which CPU it's on */ + + /* Could add cancelled - not sure how I would display it */ + float kbread, kbwritten; + /* Lots more to add */ +} samplet; + +/* Information for one process and all it's children in the main process store */ +typedef struct processinfo_s { + char* name; + pidstatTimeT lastinterest; /* Time a sample last came in */ + unsigned int pid; /* Note not pid_t - remote machine might have larger pid_t */ + /* Number of display cells that reference us */ + int count; + samplet* samples; + struct processinfo_s *next; +} processinfo; + +/* These hold a temporary copy of process data as they are read in but before they +are merged into the main process list - they are a separate type because they +contain a sample */ +typedef struct processtmp_s { + char* name; + pidstatTimeT lastinterest; /* Time a sample last came in */ + unsigned int pid; + samplet onesample; + struct processtmp_s* next; + + /* Filled in by the merging process */ + processinfo* mainprocess; + float score; +} processtmp; + +typedef struct { + GLint WindH, WindW; + GLXContext *glx_context; + rotator *rot; + trackball_state *trackball; + Bool button_down_p; +} glpidstatstruct; + +/* There is one of these per screen */ +static glpidstatstruct *glpidstat = (glpidstatstruct *) NULL; + +typedef struct { + pid_t childprocess; + int fdtochild; + + /* Process chain */ + processinfo* processes; + + /* 2D array of process pointers - one for each cell on the display */ + processinfo** bindings; + + /* The temporary list built while reading the samples in */ + processtmp* tmplist; + + int numCPUs; + + /* last time of any sample we got - 'now' as far as the far pidstat knows */ + pidstatTimeT lastsample; + + /* An array of scores for the last row */ + float* lastrowscores; + char tmpcommandname[LINEBUFFERSIZE]; + int haveGotHeader; + + texture_font_data *texfont; + + +} glpidstatglobalstruct; + +/* This is global for all screens */ +static glpidstatglobalstruct *glpidstatglob = (glpidstatglobalstruct*) NULL; + +/*************************************************************************/ + +static const float front_shininess[] = {0.0}; +static const float front_specular[] = {0.1, 0.1, 0.1, 1.0}; +static const float ambient[] = {0.0, 0.0, 0.0, 1.0}; +static const float diffuse[] = {1.0, 1.0, 1.0, 1.0}; +static const float position0[] = {1.0, 1.0, 1.0, 0.0}; +static const float position1[] = {-1.0, -1.0, 1.0, 0.0}; +static const float lmodel_ambient[] = {0.5, 0.5, 0.5, 1.0}; +static const float lmodel_twoside[] = {GL_FALSE}; + +static const float MaterialBase[] = {0.0, 0.0, 0.0, 1.0}; + +static processinfo* getProcess(int stripe, int sample) +{ + return glpidstatglob->bindings[sample*numStripes+stripe]; +} + +/* As above but where you need to modify the binding - ah for references */ +static processinfo** getProcessRef(int stripe, int sample) +{ + return &(glpidstatglob->bindings[sample*numStripes+stripe]); +} + +static processinfo* findProcessByNameAndID(const char* name, unsigned int pid) +{ + processinfo* cur; + + for(cur=glpidstatglob->processes;cur; cur=cur->next) + { + if ((cur->pid==pid) && (strcmp(name,cur->name)==0)) + return cur; + } + + return NULL; +} + +/* Returns NULL if there is no process bound to the given cell */ +static samplet* getSample(int stripe, int sample) +{ + processinfo* p=getProcess(stripe, sample); + + if (!p) return NULL; + + return &(p->samples[sample]); +} + +/* Where we shuffle the data up but don't have any process info this cycle assume it did nothing + - we might want to average something here */ +static void +clearSample(samplet *s) +{ + s->user=0.0; + s->system=0.0; + s->guest=0.0; + s->total=0.0; + s->curCPU=0; + + s->kbread=0.0; + s->kbwritten=0.0; +} + +static float +scoreSample(samplet *s) +{ + float res=1.0; + float ioscore=0.0; + float tmp; + + /* This needs to get smarter as we add other parts of the pidstat output into the sample */ + res=res*s->total; + + tmp=s->kbread/maxRangeIO; + if (tmp>1.0) tmp=1.0; + ioscore+=tmp; + + tmp=s->kbwritten/maxRangeIO; + if (tmp>1.0) tmp=1.0; + ioscore+=tmp; + + return res+ioscore; +} + +/* +Walk the process list and delete any process that has no references and hasn't +had any for a while - we don't just take out ones with no reference since they +might have useful data just none in the last sample */ +static void +removeOldProcesses(void) +{ + processinfo* curp; + processinfo** lastp; /* Pointer to modify if we delete curp to fix the list */ + pidstatTimeT now=glpidstatglob->lastsample; + + lastp=&(glpidstatglob->processes); + curp=glpidstatglob->processes; + + while (curp) + { + if ((curp->count==0) && (curp->lastinterest<(now-samplesPerStripe))) + { + /* Delete */ + processinfo *nextp=curp->next; + + *lastp=nextp; + + free(curp->samples); + free(curp->name); + /* TODO: Possibly an image of the name coming here */ + free(curp); + + curp=nextp; + /* lastp stays the same */ + + } else { + lastp=&(curp->next); + curp=curp->next; + } + }; +} + +/* Display text for a process so that the bottom left of the text is at x,y - width should + be the desired height of the text */ +static void +addProcessText(char* text, float x, float y, float width) +{ + int textwidth, textheight; + textwidth=texture_string_width(glpidstatglob->texfont,text,&textheight); + glPushMatrix(); + /* This should make 0,0 be the top of our sample */ + glTranslatef(x+width,y,0); + /* We base our scaling off the height which should be mostly constant - + so x scaling is also by that */ + glScalef(width/textheight,width/textheight,1.0); + /* make text point up */ + glRotatef(90.0, 0, 0.0, 1.0); + glDisable (GL_DEPTH_TEST); + glEnable(GL_TEXTURE_2D); + glColor3f(1.0,1.0,1.0); + print_texture_string(glpidstatglob->texfont,text); + glDisable(GL_TEXTURE_2D); + glEnable (GL_DEPTH_TEST); + glPopMatrix(); +} + +static Bool +draw_glpidstat_strip(ModeInfo * mi) +{ + glpidstatstruct *mp = &glpidstat[MI_SCREEN(mi)]; + int stripe,sample; + float stripeWidth=2.0/numStripes; + float sampleHeight=2.0/samplesPerStripe; + float traceWidth=stripeWidth/2.0; + float traceStep=stripeWidth/(2*(glpidstatglob->numCPUs-1)); + + /* -1,1 is top left, 1,-1 is bottom right */ + for(stripe=0;stripecurCPU:0; + traceLeft=stripeLeft+traceStep*cpu+traceWidth*0.75; + traceRight=traceLeft+traceWidth*0.25; + + if ((sample!=0) && (previousProcess!=currentProcess)) + { + glColor3f(0.0,0.0,0.0); + + /* Something different when the process changes */ + glVertex3f(traceLeft, sampleTop+sampleHeight/2,0); + glVertex3f(traceRight,sampleTop+sampleHeight/2,0); + + /* Split the strip so we can do other stuff */ + glEnd(); + + glBegin(GL_QUAD_STRIP); + } + + if (cursample) + { + float colread, colwrite; + + colread=cursample->kbread/maxRangeIO; + colwrite=cursample->kbwritten/maxRangeIO; + if (colread>1.0) colread=1.0; + if (colwrite>1.0) colwrite=1.0; + + /* Bias blue on a bit so it's not all black if idleish */ + glColor3f(colread, colwrite,0.2); + + } else { + glColor3f(0.0,0,0.2); + } + + glVertex3f(traceLeft, sampleTop,0); + glVertex3f(traceRight,sampleTop,0); + } + glEnd(); + glBegin(GL_QUAD_STRIP); + + previousProcess=NULL; + currentProcess=NULL; + + /* 2nd pass through the samples - CPU load, stirp to the right and text for the process names */ + for(sample=0;samplecurCPU:0; + traceLeft=stripeLeft+traceStep*cpu; + traceRight=traceLeft+traceWidth*0.75; + + if ((sample!=0) && (previousProcess!=currentProcess)) + { + glColor3f(0.0,0.0,0.0); + + /* Something different when the process changes , need + to break the stripe and display the text*/ + glVertex3f(traceLeft, sampleTop+sampleHeight/2 ,0); + glVertex3f(traceRight,sampleTop+sampleHeight/2,0); + + /* Split the strip so we can do other stuff */ + glEnd(); + + if (previousProcess!=NULL) + { + addProcessText(previousProcess->name, stripeLeft, sampleTop, stripeWidth); + } + glBegin(GL_QUAD_STRIP); + } + + if (cursample) + { + /* Bias blue on a bit so it's not all black if idleish */ + glColor3f(cursample->system, cursample->total,0.25+(cursample->user*.075)); + } else { + glColor3f(0.2,0.2,0.2); + } + + glVertex3f(traceLeft, sampleTop ,0); + glVertex3f(traceRight,sampleTop,0); + } + glEnd(); + /* Add text to identify the process at the bottom of the strip */ + if (currentProcess) + { + addProcessText(currentProcess->name, stripeLeft, -1.0, stripeWidth); + } + + } + + return True; +} + +ENTRYPOINT void +reshape_glpidstat (ModeInfo * mi, int width, int height) +{ + glpidstatstruct *mp = &glpidstat[MI_SCREEN(mi)]; + + glViewport(0, 0, mp->WindW = (GLint) width, mp->WindH = (GLint) height); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + /* x range, y range, z range ?*/ + glFrustum(-1.0, 1.0, -1.0, 1.0, 5, 15.0); + glMatrixMode(GL_MODELVIEW); + if (width >= 1024) { + glLineWidth(3); + glPointSize(3); + } else if (width >= 512) { + glLineWidth(2); + glPointSize(2); + } else { + glLineWidth(1); + glPointSize(1); + } +} + +static void +pinit(void) +{ + glClearDepth(1.0); + glClearColor(0.0, 0.0, 0.0, 1.0); + + glLightfv(GL_LIGHT0, GL_AMBIENT, ambient); + glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse); + glLightfv(GL_LIGHT0, GL_POSITION, position0); + glLightfv(GL_LIGHT1, GL_AMBIENT, ambient); + glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse); + glLightfv(GL_LIGHT1, GL_POSITION, position1); + glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient); + glLightModelfv(GL_LIGHT_MODEL_TWO_SIDE, lmodel_twoside); + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + glEnable(GL_LIGHT1); + glEnable(GL_NORMALIZE); + glEnable(GL_COLOR_MATERIAL); + glFrontFace(GL_CCW); + glCullFace(GL_BACK); + + /* glpidstat */ + glShadeModel(GL_SMOOTH); + glEnable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + clear_gl_error(); +} + + + +ENTRYPOINT void +release_glpidstat (ModeInfo * mi) +{ + if (glpidstat != NULL) { + (void) free((void *) glpidstat); + glpidstat = (glpidstatstruct *) NULL; + } + FreeAllGL(mi); +} + +ENTRYPOINT Bool +glpidstat_handle_event (ModeInfo *mi, XEvent *event) +{ + glpidstatstruct *mp = &glpidstat[MI_SCREEN(mi)]; + + if (event->xany.type == ButtonPress && + event->xbutton.button == Button1) + { + mp->button_down_p = True; + gltrackball_start (mp->trackball, + event->xbutton.x, event->xbutton.y, + MI_WIDTH (mi), MI_HEIGHT (mi)); + return True; + } + else if (event->xany.type == ButtonRelease && + event->xbutton.button == Button1) + { + mp->button_down_p = False; + return True; + } + else if (event->xany.type == ButtonPress && + (event->xbutton.button == Button4 || + event->xbutton.button == Button5)) + { + gltrackball_mousewheel (mp->trackball, event->xbutton.button, 10, + !!event->xbutton.state); + return True; + } + else if (event->xany.type == MotionNotify && + mp->button_down_p) + { + gltrackball_track (mp->trackball, + event->xmotion.x, event->xmotion.y, + MI_WIDTH (mi), MI_HEIGHT (mi)); + return True; + } + + return False; +} + + +/*************************************************************************/ +/* Input/child code to get data from pidstat */ +/*************************************************************************/ + +/* The header line ends in (x CPU) where x is the number of CPUs - of + course that may be multidigit */ +static int +processHeaderLine(char* buffer) +{ + char* openbracket; + + /* Look for the ( before the number */ + openbracket=strrchr(buffer,'('); + if (openbracket!=NULL) + { + int numCPUs; + + fprintf(stderr,"processHeaderLine with openbracket: %s\n", openbracket); + + numCPUs=atoi(openbracket+1); + + if (numCPUs>0) + { + glpidstatglob->numCPUs=numCPUs; + return 1; + } + } + + return 0; +} + +static void +mergeTempProcesses(void) +{ + processtmp* curtp; + processtmp** lasttp; /* Used for deleting off the temp list */ + int curstripe; + + + + /*fprintf(stderr,"mergeTempProcesses 1st loop (processes/scores):");*/ + /* Work down the temp list finding if they already have matching processes + in the main process list and giving them scores */ + for(curtp=glpidstatglob->tmplist;curtp;curtp=curtp->next) + { + curtp->mainprocess=findProcessByNameAndID(curtp->name, curtp->pid); + curtp->score=scoreSample(&(curtp->onesample)); + + /*fprintf(stderr,"(%p, %p, %f) ",curtp, curtp->mainprocess, curtp->score);*/ + } + + /*fprintf(stderr,"\n mergeTempProcesses 2nd loop (bottom row update) : ");*/ + /* For any process that is on the temporary list that is on the bottom row + already (i.e. was on the old bottom row) then just update that process + and move along. + In this pass we also add new processes to the process list and add + the sample in the temporary to the main process */ + lasttp=&(glpidstatglob->tmplist); + curtp=glpidstatglob->tmplist; + while (curtp) + { + int found; + processinfo* mainproc=curtp->mainprocess; + + /*fprintf(stderr," %p, ", curtp);*/ + found=0; + if (mainproc) + { + for(curstripe=0; + (!found) && (curstripename=strdup(curtp->name); + newproc->lastinterest=0; /* Gets tweaked below */ + newproc->pid=curtp->pid; + newproc->count=0; + newproc->samples=calloc(samplesPerStripe,sizeof(samplet)); + if (!newproc->samples) + { + fprintf(stderr,"Failed to allocate sample data\n"); + exit(1); + } + + /* Add to head */ + newproc->next=glpidstatglob->processes; + glpidstatglob->processes=newproc; + + /* Mod the temporary list entry to point to it */ + curtp->mainprocess=newproc; + + mainproc=newproc; + } + + /* Add the temporary process sample to the process - even if we don't use it now */ + mainproc->lastinterest=curtp->lastinterest; + mainproc->samples[samplesPerStripe-1]=curtp->onesample; + /*fprintf(stderr, " %p, %d, ", mainproc, found?curstripe:-1);*/ + + if (found) + { + /* The temp process is on the bottom row already - nothing to be done */ + processtmp* next=curtp->next; + + /* Delete this temporary list entry */ + *lasttp=next; + free(curtp->name); + free(curtp); + curtp=next; + /* lasttp stays the same since we've deleted this entry */ + } else { + /* Just move along - this entry isn't on the bottom row + we'll get around to it in the next pass */ + lasttp=&(curtp->next); + curtp=curtp->next; + } + }; + + /* Fill in scores for the last row */ + if (!glpidstatglob->lastrowscores) + { + glpidstatglob->lastrowscores=calloc(numStripes,sizeof(float)); + if (!glpidstatglob->lastrowscores) + { + fprintf(stderr,"Failed to allocate last row data\n"); + exit(1); + } + } + + /*fprintf(stderr,"\n mergeTempProcesses 3rd loop (last row scores) ");*/ + for(curstripe=0;curstripelastrowscores[curstripe]=scoreSample(&(proc->samples[samplesPerStripe-1])); + if (glpidstatglob->lastrowscores[curstripe]==0.0) + { + /* So there is no valid sample data at that row - or it's all 0 + what we need to do is make processes which have hadn't had any interesting + data for a long time be move -ve - lastinterest should be less than the + global lastsample except where it has been just updated and still + has a 0 score */ + glpidstatglob->lastrowscores[curstripe]=proc->lastinterest-glpidstatglob->lastsample; + } + } else { + /* Lower score than any real process */ + glpidstatglob->lastrowscores[curstripe]=-1000000.0; + } + + /*fprintf(stderr, "%f ", glpidstatglob->lastrowscores[curstripe]);*/ + } + + /*fprintf(stderr,"\n mergeTempProcesses 4th loop (add new bottom rows) ");*/ + /* Now go through the temporary list again - this time what's left on it isn't + on the bottom row so we have to decide if to add it and where based on scores + + Note even though this tmplist entry isn't in the bottom row, it's process + entry was added and it's sample stored in the previous loop */ + lasttp=&(glpidstatglob->tmplist); + curtp=glpidstatglob->tmplist; + while (curtp) + { + processinfo* mainproc=curtp->mainprocess; + processtmp* next; + + int bestmatch=-1; /* Stripe with lowest score */ + float bestscore=MAXFLOAT; + unsigned int curstripe=0; + + + /* I did wonder about sorting this list - but we have to update + after each mod anyway */ + for(curstripe=0;curstripelastrowscores[curstripe]lastrowscores[curstripe]; + bestmatch=curstripe; + } + } + + /*fprintf(stderr, " (%f %f %d ) ", curtp->score, bestscore, bestmatch);*/ + /* If the temp process has a higher score than the lowest + scoring entry in the last row then insert the temp. + + Note we might want to put some flipping limit in here, e.g. + don't flip if it just got flipped? */ + + if (curtp->score > bestscore) + { + /* Get a pointer to the cell to change */ + processinfo** toreplaceprocref=getProcessRef(bestmatch, samplesPerStripe-1); + + /* If there is something there then we need to dec it's use */ + if (*toreplaceprocref) + (*toreplaceprocref)->count--; + + *toreplaceprocref=mainproc; + mainproc->count++; + + /* Update the scores */ + glpidstatglob->lastrowscores[bestmatch]=curtp->score; + } + + /* Delete this temp process entry */ + next=curtp->next; + + *lasttp=next; + free(curtp->name); + free(curtp); + curtp=next; + /* lasttp stays the same since we've deleted this entry */ + + }; +} + +#define EXPECTEDFORMAT "# Time PID %usr %system %guest %CPU CPU minflt/s majflt/s VSZ RSS %MEM kB_rd/s kB_wr/s kB_ccwr/s Command" +static void +checkFormatLine(char* buffer) +{ + if (strcmp(buffer,EXPECTEDFORMAT)!=0) + { + fprintf(stderr,"pidstat format mismatch - check options and version; we expected: %s\nbut got %s\n", EXPECTEDFORMAT, buffer); + exit(1); + } + + /* This needs to do the rolling up */ + { + processinfo* curp; + unsigned int curs; + + /* Roll all the samples along */ + for(curp=glpidstatglob->processes;curp;curp=curp->next) + { + memmove(curp->samples,&(curp->samples[1]),sizeof(curp->samples[0])*samplesPerStripe); + clearSample(&(curp->samples[samplesPerStripe-1])); + } + + /* Now move the process bindings up */ + /* Loop accross the stripes removing the process that's about to go off the top*/ + for(curs=0;curscount--; + + /* Inc the count of the bottom sample's process of the stripe because it's + about to become the bottom and 2nd to bottom */ + curp=getProcess(curs,samplesPerStripe-1); + if (curp) + curp->count++; + } + + /* Now move the whole array up */ + /* Since we moved the old last line still has the old pointers in - and that's a good + start - note we incremented their count before */ + memmove(glpidstatglob->bindings, glpidstatglob->bindings+numStripes, + sizeof(processinfo*)*numStripes*(samplesPerStripe-1)); + + mergeTempProcesses(); + + /* Any process on the bottom row that hasn't had anything interesting in a while + we remove from the bottom row, eventually it will go off the top and + eventually get cleaned up by removeOldProcesses */ + for(curs=0;curslastsample; + + curp=getProcess(curs,samplesPerStripe-1); + if ((curp) && (curp->lastinterest<(now-samplesPerStripe))) + { + /* Remove from bottom row */ + *(getProcessRef(curs,samplesPerStripe-1))=NULL; + curp->count--; + } + } + + /* Remove any process that haven't seen any action lately */ + removeOldProcesses(); + } +} + +static void +processDataLine(char* buffer) +{ + long eventtime; + unsigned int pid; + float user,system,guest,totalcpu; + unsigned int curCPU; + float minorfaults, majorfaults; + long vsz,rss; + float percentmem; + float kbread, kbwritten, kbcancelledwrite; + float contextswitches, nonvolcs; + int scanfRet; + processtmp* ourtmpp; + + scanfRet=sscanf(buffer, "%ld %u %f %f %f %f %u %f %f %ld %ld %f %f %f %f %s", + &eventtime,&pid, + &user,&system,&guest,&totalcpu,&curCPU, + &minorfaults,&majorfaults, + &vsz, &rss, &percentmem, + &kbread, &kbwritten, &kbcancelledwrite, + /* &contextswitches, &nonvolcs, */ + glpidstatglob->tmpcommandname); + + if (scanfRet!=16) + { + fprintf(stderr,"processDataLine got %d items read from %s\n", scanfRet, buffer); + exit(1); + }; + + /* Hmm insert somewhere, somehow */ + /* Create an entry on the temporary process list */ + if (ourtmpp=(processtmp*)malloc(sizeof(processtmp)), ourtmpp==NULL) + { + fprintf(stderr,"processDataLine failed to allocate temporary process\n"); + exit(1); + } + + /* Glue it into the list */ + ourtmpp->name=strdup(glpidstatglob->tmpcommandname); + ourtmpp->lastinterest=eventtime; + ourtmpp->pid=pid; + + /* Sample values are 0...1 */ + ourtmpp->onesample.user=user/100.0; + ourtmpp->onesample.system=system/100.0; + ourtmpp->onesample.guest=guest/100.0; + ourtmpp->onesample.total=totalcpu/100.0; + ourtmpp->onesample.curCPU=curCPU; + ourtmpp->onesample.kbread=kbread; + ourtmpp->onesample.kbwritten=kbwritten; + + ourtmpp->next=glpidstatglob->tmplist; + glpidstatglob->tmplist=ourtmpp; + + if (eventtime>glpidstatglob->lastsample) + glpidstatglob->lastsample=eventtime; +} + +/* The types of lines we get are: + 1) Header line - which contains the number of CPUs + 2) The format line which is the user readable line - we can check + that to check that the data line is in the format we expect + (if we get really smart we can start parsing it to make it cope + with changes and allow the user to extract any field) + 3) Data lines - one line per process sample + (3a if we get round to it thread lines if we add -t) + 4) A blank line we ignore + + We might also get some random crud if the user has started the pidstat + via a bad ssh or the like - ideally we could do with ignoring it */ +static void +processLine (char* buffer) +{ + int lineLength=strlen(buffer); + + /* I've seen the line end in a \r - I'm not sure why yet (is this the pty ?) + - strip it */ + if ((lineLength>0) && (buffer[lineLength-1]='\r')) + { + buffer[lineLength-1]='\0'; + lineLength--; + } + + if (!glpidstatglob->haveGotHeader) + { + /* The header ends in CPU) */ + if ((lineLength>4) && (strcmp("CPU)", buffer+(lineLength-4))==0)) + { + glpidstatglob->haveGotHeader=processHeaderLine(buffer); + }; + } else { + /* OK we have a header, so we're into real data */ + if (buffer[0]=='#') + { + checkFormatLine(buffer); + } else if (lineLength==0) { + /* Empty line - ignore */ + } else { + /* This had better be a data line */ + processDataLine(buffer); + } + } +} + +/* + * Called by an XtAppAddInput event + * The file descriptor is non blocking, so try and read and do interesting + * things when we get a line + */ +static void +incomingDataFunc (XtPointer closure, int *source, XtInputId *id) +{ + static char linebuffer[LINEBUFFERSIZE]; + static unsigned int nextFree=0; /* Next free point in linebuffer */ + int done=0; + + do + { + char* lf; + + /* Reinitialises empty or emptied buffer */ + if (nextFree==0) linebuffer[0]='\0'; + + lf=strchr(linebuffer,'\n'); + if (lf==NULL) + { + int amountread; + + /* -1 on size to allow for a null - nextFree is never that big due to checks on previous read */ + amountread=read(glpidstatglob->fdtochild,linebuffer+nextFree,(LINEBUFFERSIZE-nextFree)-1); + + if (amountread<=0) + { + /* No lf and nothing read, nothing to do */ + /* TODO: What to do on error */ + done=1; + } else { + nextFree+=amountread; + linebuffer[nextFree]='\0'; + + /* Now check for overlength lines */ + lf=strchr(linebuffer,'\n'); + if ((lf==NULL) && (nextFree>=(LINEBUFFERSIZE-1))) + { + /* We have no lf and we have a full buffer - give up on it */ + fprintf(stderr,"Overlength none-line\n"); + nextFree=0; + } + } + + } + + /* So check lf again - it's none-NULL if it was non-null or if we've just + gained a lf */ + if (lf!=NULL) + { + *lf='\0'; + processLine(linebuffer); + /* shuffle everything after the line down to the beginning of the buffer */ + memmove(linebuffer, lf+1, LINEBUFFERSIZE-((lf+1)-linebuffer)); + nextFree-=((lf+1)-linebuffer); + } + } while (!done); +} + +/* Called from after the fork, stdin/out/err are already wired */ +static void +startChild (void) +{ + /* Make sure the output we get is in the format we expect + without format being changed by locales */ + putenv("LANG=C"); + putenv("LC_ALL=C"); + + /* Let system take care of any options the user may have specified on the command; thanks */ + system(pidstatcommandstring); + + /* If this has gone wrong there isn't actually much we can do */ + exit(1); +} + +/*************************************************************************/ +/* The exported interfaces */ +/*************************************************************************/ +ENTRYPOINT void +init_glpidstat (ModeInfo * mi) +{ + pid_t pid; + glpidstatstruct *mp; + int pipefd[2]; + + /* Per screen struct */ + if (glpidstat == NULL) { + if ((glpidstat = (glpidstatstruct *) calloc(MI_NUM_SCREENS(mi), + sizeof (glpidstatstruct))) == NULL) + return; + } + + /* global struct with all process data */ + if (glpidstatglob == NULL) { + glpidstatglob = (glpidstatglobalstruct *) calloc(1, sizeof(glpidstatglobalstruct)); + if (glpidstatglob == NULL) return; + + glpidstatglob->bindings = (processinfo**)calloc(numStripes*samplesPerStripe,sizeof(processinfo*)); + if (glpidstatglob->bindings == NULL) return; + + /* We read this properly from the pidstat output later */ + glpidstatglob->numCPUs=1; + glpidstatglob->lastsample=0; + } + + mp = &glpidstat[MI_SCREEN(mi)]; + { + double rot_speed = 0.0; + mp->rot = make_rotator (rot_speed, rot_speed, rot_speed, 1, 0, True); + mp->trackball = gltrackball_init (); + } + + if ((mp->glx_context = init_GL(mi)) != NULL) { + + reshape_glpidstat(mi, MI_WIDTH(mi), MI_HEIGHT(mi)); + glDrawBuffer(GL_BACK); + pinit(); + } else { + MI_CLEARWINDOW(mi); + } + if (!glpidstatglob->texfont) + glpidstatglob->texfont=load_texture_font (MI_DISPLAY(mi), "font"); + +#if 0 + /* If I use this pipe here the GL stops working - so I used that openpty + below - this bugs me */ + if (pipe(pipefd)==-1) + { + perror("Pipe create"); + return; + } +#endif + if (openpty(&(pipefd[0]),&(pipefd[1]),NULL,NULL,NULL)==-1) + { + perror("openpty"); + return; + }; + glpidstatglob->fdtochild=pipefd[0]; + switch (pid=fork()) + { + case 0: /* Child */ + close(0); + close(pipefd[0]); /* That's our copy of the side the parent reads from */ + /* Swizzle it to stdout */ + if (dup2(pipefd[1],1)==-1) + { + perror("dup2"); + exit(1); + } + close(pipefd[1]); /* It's now on our stdout */ + startChild(); + /* We shouldn't get here, and if we do there isn't much we can do about it */ + exit(0); + + case -1: + /* Error */ + free(glpidstat); + free(glpidstatglob); + return; + + default: /* Parent */ + { + int oldflags; + glpidstatglob->childprocess=pid; + /* I think I need the file descriptor to be non-blocking so that I don't hang on a short + read when XtAppAddInput calls me back */ + + oldflags=fcntl(glpidstatglob->fdtochild, F_GETFL); + if (oldflags!=-1) + { + fcntl(glpidstatglob->fdtochild, F_SETFL, (long)(oldflags | O_NONBLOCK)); + }; + + /* Note this returns a handle that can be used to remove it later */ + XtAppAddInput(XtDisplayToApplicationContext(mi->dpy), glpidstatglob->fdtochild, (XtPointer) (XtInputReadMask | XtInputExceptMask), + incomingDataFunc, NULL /* client data */); + } + break; + + + } +} + +ENTRYPOINT void +draw_glpidstat (ModeInfo * mi) +{ + glpidstatstruct *mp; + + Display *display = MI_DISPLAY(mi); + Window window = MI_WINDOW(mi); + + if (glpidstat == NULL) + return; + mp = &glpidstat[MI_SCREEN(mi)]; + + MI_IS_DRAWN(mi) = True; + + if (!mp->glx_context) + return; + + glXMakeCurrent(display, window, *(mp->glx_context)); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glPushMatrix(); + + glTranslatef(0.0, 0.0, -5.0); /* Compare with the Frustum above */ + + /* gltrackball_rotate (mp->trackball); */ + +/* + { + double x, y, z; + get_rotation (mp->rot, &x, &y, &z, !mp->button_down_p); + glRotatef (x * 360, 1.0, 0.0, 0.0); + glRotatef (y * 360, 0.0, 1.0, 0.0); + glRotatef (z * 360, 0.0, 0.0, 1.0); + } +*/ + /* glpidstat */ + if (!draw_glpidstat_strip(mi)) { + release_glpidstat(mi); + return; + } + + glPopMatrix(); + + if (MI_IS_FPS(mi)) do_fps (mi); + glFlush(); + + glXSwapBuffers(display, window); + + /* Check for data ready to import */ +} + +#ifndef STANDALONE +ENTRYPOINT void +change_glpidstat (ModeInfo * mi) +{ + glpidstatstruct *mp = &glpidstat[MI_SCREEN(mi)]; + + if (!mp->glx_context) + return; + + glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(mp->glx_context)); + pinit(); +} +#endif /* !STANDALONE */ + + +XSCREENSAVER_MODULE ("Glpidstat", glpidstat) + +#endif diff -urN tmp/orig/xscreensaver-5.07/hacks/glx/glpidstat.man xscreensaver-5.07/hacks/glx/glpidstat.man --- tmp/orig/xscreensaver-5.07/hacks/glx/glpidstat.man 1970-01-01 01:00:00.000000000 +0100 +++ xscreensaver-5.07/hacks/glx/glpidstat.man 2008-11-13 17:35:01.000000000 +0000 @@ -0,0 +1,92 @@ +.TH XScreenSaver 1 "" "X Version 11" +.SH NAME +glpidstat - display process activity from pidstat graphically +.SH SYNOPSIS +.B glpidstat +[\-display \fIhost:display.screen\fP] +[\-visual \fIvisual\fP] +[\-window] +[\-root] +[\-fps] +[\-delay \fInumber\fP] +[\-pidstatcommandstring \fIcommand\fP] +[\-numstripes \fInumber\fP] +[\-samplesperstripe \fInumber\fP] +[\-maxrangeio \fInumber\fP] +.SH DESCRIPTION +The 'pidstat' program, that is part of the systat suite, displays the state of +processes on a Linux system. This hack displays the output of pidstat +graphically so you can sit back and watch your machine. This hack +requires version 8.1.6 of pidstat (or newer). +Each process is displayed as a stripe; the stripe occupies a wider space and +moves to the left or right within the space depending on which CPU it is scheduled on. +CPU 0 is on the left of the space. The stripe itself consists of two parts, the left +side changes colour based on CPU usage (More Red for more system CPU, Green for total +and Blue for user CPU usage, but generally some combination) while the right, thinner +substripe changes colour based on IO (Red for Read, Green for Write). +.SH OPTIONS +.TP 8 +.B \-visual \fIvisual\fP +Specify which visual to use. Legal values are the name of a visual class, +or the id number (decimal or hex) of a specific visual. +.TP 8 +.B \-window +Draw on a newly-created window. This is the default. +.TP 8 +.B \-root +Draw on the root window. +.TP 8 +.B \-delay \fInumber\fP +Per-frame delay, in microseconds. Default: 20000 (0.02 seconds.). +.TP 8 +.B \-fps | \-no-fps +Whether to show a frames-per-second display at the bottom of the screen. +.TP 8 +.B \-pidstatcommandstring \fIcommand\fP +The command to use to run pidstat. glpidstat requires the use of the +h, u,r, and d options on pidstat to get all the output in the format +it wants. If you have ssh keys setup you can display the state of another +machine - e.g. +.TP 8 +.B \-numstripes \fInumber\fP +The number of processes to display at the same time accross the window. +.TP 8 +.B \-samplesperstripe \fInumber\fP +The number of samples shown vertically per stripe, each sample corresponds +to one set of output from pidstat. +.TP 8 +.B \-maxrangeio \fInumber\fP +The amount of IO (in KB/s) which cause the colour of the display to be full brightness. +.SH ENVIRONMENT +.PP +.TP 8 +.B DISPLAY +to get the default host and display number. +.TP 8 +.B XENVIRONMENT +to get the name of a resource file that overrides the global resources +stored in the RESOURCE_MANAGER property. +.SH EXAMPLES +.B glpidstat -pidstatcommandstring '/my/sysstat/source/pidstat -h -u -r -d 1' +.RS +Run your own installation of pidstat at an arbitrary location. +.RE +.B glpidstat -pidstatcommandstring 'ssh bigbox pidstat -h -u -r -d 1'. +.RS +Run pidstat on a remote box (with an ssh key or other system where you don't need to +enter a password). +.RE +.SH SEE ALSO +.BR X (1), +.BR xscreensaver (1) +.BR pidstat (1) +.SH COPYRIGHT +Copyright \(co 2008 by Dr. David Alan Gilbert. Permission to use, copy, modify, +distribute, and sell this software and its documentation for any purpose is +hereby granted without fee, provided that the above copyright notice appear +in all copies and that both that copyright notice and this permission notice +appear in supporting documentation. No representations are made about the +suitability of this software for any purpose. It is provided "as is" without +express or implied warranty. +.SH AUTHOR +Dr. David Alan Gilbert (dave@treblig.org) diff -urN tmp/orig/xscreensaver-5.07/hacks/glx/Makefile.in xscreensaver-5.07/hacks/glx/Makefile.in --- tmp/orig/xscreensaver-5.07/hacks/glx/Makefile.in 2008-08-11 06:12:01.000000000 +0100 +++ xscreensaver-5.07/hacks/glx/Makefile.in 2008-11-13 17:44:05.000000000 +0000 @@ -103,7 +103,7 @@ antinspect.c providence.c pinion.c involute.c boing.c \ texfont.c carousel.c fliptext.c antmaze.c tangram.c \ tangram_shapes.c crackberg.c glhanoi.c cube21.c \ - timetunnel.c juggler3d.c topblock.c glschool.c \ + timetunnel.c juggler3d.c topblock.c glschool.c glpidstat.c \ glschool_gl.c glschool_alg.c glcells.c voronoi.c \ moebiusgears.c lockward.c cubicgrid.c hypnowheel.c \ skytentacles.c teapot.c @@ -138,7 +138,7 @@ antinspect.o providence.o pinion.o involute.o boing.o \ texfont.o carousel.o fliptext.o antmaze.o tangram.o \ tangram_shapes.o crackberg.o glhanoi.o cube21.o \ - timetunnel.o juggler3d.o topblock.o glschool.o \ + timetunnel.o juggler3d.o topblock.o glschool.o glpidstat.o \ glschool_gl.o glschool_alg.o glcells.o voronoi.o \ moebiusgears.o lockward.o cubicgrid.o hypnowheel.o \ skytentacles.o teapot.o @@ -151,7 +151,7 @@ endgame glblur flurry atunnel flyingtoasters bouncingcow \ glslideshow jigglypuff klein hypertorus glmatrix cubestorm \ glknots blocktube flipflop antspotlight polytopes \ - gleidescope mirrorblob blinkbox noof polyhedra \ + gleidescope mirrorblob blinkbox noof polyhedra glpidstat \ antinspect providence pinion boing carousel fliptext \ antmaze tangram crackberg glhanoi cube21 timetunnel \ juggler3d topblock glschool glcells voronoi moebiusgears \ @@ -187,7 +187,7 @@ pulsar.man queens.man rubik.man sballs.man sierpinski3d.man \ spheremonics.man sproingies.man stairs.man starwars.man \ stonerview.man superquadrics.man xscreensaver-gl-helper.man \ - endgame.man flurry.man glblur.man atunnel.man \ + endgame.man flurry.man glblur.man atunnel.man glpidstat.man \ flyingtoasters.man bouncingcow.man glslideshow.man \ jigglypuff.man klein.man hypertorus.man glmatrix.man \ cubestorm.man glknots.man blocktube.man flipflop.man \ @@ -606,6 +606,9 @@ glknots: glknots.o tube.o $(HACK_TRACK_OBJS) $(CC_HACK) -o $@ $@.o tube.o $(HACK_TRACK_OBJS) $(HACK_LIBS) +glpidstat: glpidstat.o texfont.o $(HACK_TRACK_OBJS) + $(CC_HACK) -o $@ $@.o texfont.o $(HACK_TRACK_OBJS) $(HACK_LIBS) -lutil + blocktube: blocktube.o xpm-ximage.o $(HACK_OBJS) $(CC_HACK) -o $@ $@.o xpm-ximage.o $(HACK_OBJS) $(XPM_LIBS)