// spy.cc -- engine performance monitor
//
// Author: Ian.Piumarta@inria.fr
//
// last edited: Tue Feb 23 17:33:15 1999 by piumarta (Ian Piumarta) on pingu

#include "config.h"

#ifdef SPY

#include <stdio.h>
#include <string.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include "interp.h"
#include "translate.h"

extern "C" {
  int lengthOf(oop object);
};

#undef	USE_SQUEAK_WINDOW
#define	NO_SPY_TITLE

/* name of spy window in Xrm and the WM */
#define ClassName	"SqueakCompilerSpy"
#define WindowName	"squeakCompiler spy"

extern Display	*stDisplay;	// Squeak's Display
extern Window	 stWindow;	// Squeak's Window
extern Visual	*stVisual;	// Squeak's default visual
extern GC	 stGC;		// Squeak's graphics context
extern XColor	 stColorBlack;	// Squeak's idea of a black pixel
extern XColor	 stColorWhite;	// Squeak's idea of a white pixel
extern Colormap	 stColormap;	// Squeak's colour map

// pixel values as defined in the Squeak colour map for 8-bit (PseudoColor).
// deeper displays (with TrueColor visuals) might not grok
#if 0
# define SqueakWhite	0
# define SqueakBlack	1
#else
# define SqueakWhite	stColorWhite.pixel
# define SqueakBlack	stColorBlack.pixel
#endif

static Window	 window;	// our window

static bool	 enabled= false;
static XFontStruct *fontStruct;	// for snarfing character properties
static Font	 font;

#define	BORDER	 4	// pixel indent
#define	NCOLS	80
#define	NROWS	 8
#define	LINESEP	 2	// pixels: raster bottom to raster top on next line
#define BARSIZE  6	// height of graphics bars

static unsigned	 charWidth;	// raster left to raster right
static unsigned	 charHeight;	// raster top to raster bottom
static unsigned	 charBaseline;	// raster top to baseline
static unsigned	 baselineSkip;	// distance between baselines with lineskip

static char	*transPrefix= "compiling:  ";
static int	 transPrefixLen;

static oop	 lastYoung= 0;
static oop	 minYoung= 0;


// called from jitter initialisation

void spyInitialise(void)
{
  int char_X, char_p;

  transPrefixLen= strlen(transPrefix);

  fontStruct= XLoadQueryFont(stDisplay, "fixed");
  font= fontStruct->fid;
  // font dimensions
  char_X=	'X' - fontStruct->min_char_or_byte2;
  char_p=	'p' - fontStruct->min_char_or_byte2;
  charWidth=	fontStruct->per_char[char_X].width;
  charBaseline=	fontStruct->per_char[char_X].ascent;
  charHeight=	charBaseline + fontStruct->per_char[char_p].descent;
  baselineSkip=	charHeight + LINESEP;

  XRectangle windowBounds= {
    0, 0,
    (charWidth * NCOLS) + (BORDER * 2),
    (baselineSkip * NROWS) + (BORDER * 2) + BARSIZE
  };

#ifdef USE_SQUEAK_WINDOW

  window= stWindow;

#else !USE_SQUEAK_WINDOW

  window= XCreateSimpleWindow
    (stDisplay, DefaultRootWindow(stDisplay),
     windowBounds.x, windowBounds.y,
     windowBounds.width, windowBounds.height,
     1, SqueakBlack, SqueakWhite);
  {
    XClassHint *classHints= XAllocClassHint();
    classHints->res_name= classHints->res_class= ClassName;
    XSetClassHint(stDisplay, window, classHints);
    XStoreName(stDisplay, window, WindowName);
    XFree(classHints);
  }
  {
    XWMHints *wmHints= XAllocWMHints();
    wmHints->input= True;
    wmHints->initial_state= NormalState;
    wmHints->flags= InputHint|StateHint;
    XSetWMHints(stDisplay, window, wmHints);
    XFree((void *)wmHints);
  }
  // backing store useful for event-free drawing
  {
    XSetWindowAttributes attributes;
    attributes.backing_store= Always;
    XChangeWindowAttributes(stDisplay, window, CWBackingStore, &attributes);
  }
  if (stVisual->c_class == PseudoColor) {
    XSetWindowColormap(stDisplay, window, stColormap); // humour preservation
  }
#ifdef NO_SPY_TITLE
  XSetTransientForHint(stDisplay, window, stWindow);
#endif

  XMapWindow(stDisplay, window);

#endif !USE_SQUEAK_WINDOW

  enabled= true;
  minYoung= youngStart;
}


// called from jitter release

void spyRelease(void)
{
  enabled= false;
  XDestroyWindow(stDisplay, window);
  XUnloadFont(stDisplay, font);
}


// called from translateMethodFor()

void spyTranslation(Class *cls, oop selector)
{
  if (!enabled) return;
  char message[128];
  strcpy(message, transPrefix);
  char *classSuffix;

  if (cls->sizeBits() == 32) {
    Metaclass *meta= cls->asMetaclass();
    cls= meta->thisClass;
    classSuffix= " class";
  } else {
    classSuffix= "";
  }

  Symbol *name= cls->name;
  strncat(message, (char *)name->_bytes, lengthOf(name));
  strcat(message, classSuffix);
  strcat(message, ">>");
  strncat(message, (char *)selector->_bytes, lengthOf(selector));
  XClearArea(stDisplay, window,
	     BORDER, BORDER,
	     (BORDER * 2) + charWidth * NCOLS,
	     baselineSkip,
	     False);
  XDrawString(stDisplay, window, stGC,
	      BORDER, BORDER + charBaseline,
	      message, strlen(message));

#if 1
    // this makes for smoother display, but slows down the VM
    XFlush(stDisplay);
#endif
}


static unsigned widths[NROWS];

static void displayLine(int lineNo, char *line, int len)
{
  XClearArea(stDisplay, window,
	     BORDER,
	     BORDER + (lineNo * baselineSkip),
	     charWidth * widths[lineNo],
	     baselineSkip,
	     False);
  XDrawString(stDisplay, window, stGC,
	      BORDER,
	      BORDER + (lineNo * baselineSkip) + charBaseline,
	      line, len);
  widths[lineNo]= len;
}


static int tx= 0;
static int lastUpdate;
static int majorClock= 0;
static int minorClock= 0;

extern "C" {
  int ioLowResMSecs(void);
  int ioMicroMSecs(void);
  int ioMSecs(void);
};


// called from: process change, post GC, interrupt check

void spyStatistics(void)
{
  if (!enabled) return;

  int thisUpdate= ioLowResMSecs();
  int updateDelta= thisUpdate - lastUpdate;
  lastUpdate= thisUpdate;
  majorClock-= updateDelta;
  minorClock-= updateDelta;

  if (minorClock < 0) {
    extern unsigned stat_jitterEntries;
    minorClock= 1000 / MINOR_UPDATES_PER_SEC;
    int ty= BORDER + NROWS * baselineSkip;

    XClearArea(stDisplay, window,
	       tx, ty, BORDER, BARSIZE,
	       False);

    tx= BORDER + (stat_jitterEntries % ((transPrefixLen - 2) * charWidth));

    XFillRectangle(stDisplay, window, stGC,
		   tx, ty, BORDER, BARSIZE);

    int ml= BORDER + charWidth * transPrefixLen;
    int mr= BORDER + charWidth * (NCOLS);
    int mw= mr - ml;

    XClearArea(stDisplay, window,
	       ml, ty, mr - ml, BARSIZE,
	       False);

    XDrawRectangle(stDisplay, window, stGC,
		   ml, ty, mr - ml, BARSIZE - 1);

    if (youngStart < lastYoung) minYoung= youngStart;
    lastYoung= youngStart;

    int memSize= endOfMemory - memory;
    int old= (minYoung - memory) * mw / memSize;
    int tenured= (youngStart - memory) * mw / memSize;
    int young= (freeBlock - memory) * mw / memSize;

    XFillRectangle(stDisplay, window, stGC,
		   ml, ty, old - 1, BARSIZE - 1);
    XFillRectangle(stDisplay, window, stGC,
		   ml + tenured, ty, young - tenured, BARSIZE - 1);
  }

  if (majorClock < 0) {
    majorClock= 1000 / MAJOR_UPDATES_PER_SEC;
    extern int inCsq;
    extern unsigned long long lastEntry, lastExit;
    extern unsigned long long interpMSecs, jitterMSecs;
    unsigned long long now= ioMicroMSecs();
    if (inCsq) {
      jitterMSecs+= now - lastEntry;
      lastEntry= now;
    } else {
      interpMSecs+= now - lastExit;
      lastExit= now;
    }
    char buf[NCOLS*NROWS];
    extern void printMemoStats(char *);
    printMemoStats(buf);
    int lineNo= 0;
    char *first= buf, *last= buf;
    while (*last != '\0') {
      while (*last++ != '\n');
      displayLine(++lineNo, first, last - first - 1);
      first= last;
      if (lineNo == NROWS) break;
    }
#if 0
    // this makes for smoother display, but slows down the VM
    XFlush(stDisplay);
#endif
  }
}


#endif SPY

