/* Fragit.c -- A simple memory thrasher/debugging tool.
 *
 * Copyright 1988 by Justin V. McCormick.
 *  May be freely used, modified, and distributed in any for for either
 * commercial or personal profit or non-profit, so long as this notice
 * remains prominently attached to the source code.
 *
 *  In any case, the author makes no specific performance claims
 * for this code and has no plans to maintain or support this code.
 * Additionally, the author bears no liability or responsibility if the
 * use of this code causes loss of data or sleep.  This is your final
 * notice -- you've been warned!
 */

/* Includes */
#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/memory.h>
#include <exec/ports.h>
#include <exec/io.h>
#include <exec/libraries.h>
#include <exec/interrupts.h>
#include <exec/semaphores.h>
#include <graphics/gfx.h>
#include <graphics/view.h>
#include <graphics/rastport.h>
#include <graphics/layers.h>
#include <graphics/clip.h>
#include <graphics/text.h>
#include <libraries/dos.h>
#include <devices/timer.h>
#include <devices/inputevent.h>
#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>
#include <intuition/screens.h>
#include <ctype.h>

/* External Functions */
extern struct Library *OpenLibrary();
extern VOID CloseLibrary();
extern struct Window *OpenWindow();
extern VOID CloseWindow();
extern struct Node *RemHead();
extern struct Node *RemTail();
extern struct Message *GetMsg();
extern VOID ReplyMsg();
extern BYTE *AllocMem();
extern VOID AddHead();
extern VOID FreeMem();
extern LONG AvailMem();
extern LONG *DateStamp();
extern LONG RangeRand();
extern LONG atol();

/* External Variables */
extern WORD Enable_Abort;

/* Local Definitions */

/* Local Structure Definitions */
struct FragNode			/* For dynamic list of memory blocks	*/
{
  struct MinNode fn_Node;	/* Exec node linkage			*/
  ULONG fn_Size;		/* Size of allocated block		*/
  BYTE *fn_Data;		/* Pointer to allocated block		*/
};

/* Local Functions */
WORD main();
VOID ThrashMem();
LONG AddRandomFrag();
LONG FreeRandomFrag();
struct MinList *AllocMinList();
VOID FreeMinList();
struct FragNode *AllocFragNode();
VOID PrintMemoryStats();
VOID FreeFragNode();
VOID FreeAllFragNodes();
VOID ShowUsage();
VOID CleanUp();
VOID RattleDice();
WORD GetIDCMPEvents();

/* Global Variables */
struct GfxBase *GfxBase = 0L;
struct IntuitionBase *IntuitionBase = 0L;
struct MinList *FragList = 0L;
struct Window *FragWin = 0L;
UBYTE FragWinTitle[] = "Fragit 1.0 \xa9 1988 Justin V. McCormick";
struct NewWindow NewWin =
{
  0,0,400,64,(UBYTE)0,(UBYTE)1,(ULONG)CLOSEWINDOW|RAWKEY,
  (ULONG)WINDOWDRAG|WINDOWDEPTH|WINDOWCLOSE|SMART_REFRESH|NOCAREREFRESH,
  0L,0L,FragWinTitle,0L,0L,0,0,0,0,(UWORD)WBENCHSCREEN
};
LONG AllocCount;		/* Number of FragNodes in list		*/
LONG FailCount;			/* Number of allocs that failed 	*/
LONG TotalFragBytes;		/* Number of bytes due to fn_Data frags	*/
WORD Verbose;			/* Verbose output flag			*/

/* -------------------------------------------------------------------- */
/* Program entry point.							*/
/* -------------------------------------------------------------------- */
WORD main(argc, argv)
  WORD argc;
  BYTE **argv;
{
  BYTE tstr[80];
  WORD argindex;
  register LONG minmem, minfrag, maxfrag;

/* Disable ^C processing */
  Enable_Abort = 0;

/* Open libraries */
  if ( (GfxBase = (struct GfxBase *)OpenLibrary("graphics.library", 0L)) == 0L)
    CleanUp("No GfxBase?");
  if ( (IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 0L)) == 0L)
    CleanUp("No IntuitionBase?");

/* Set defaults, parse CLI arguments */
  minmem = 100000L;
  maxfrag = 10000L;
  minfrag = 8L;
  Verbose = 0;
  argindex = 1;
  if (argc > argindex)
  {
    if (argv[argindex][0] == '-' && (WORD)_tolower(argv[argindex][1]) == (WORD)'v')
    {
      Verbose = 1;
      argindex++;
    }
    if (argc > argindex)
    {
      if (isdigit(argv[argindex][0]) == 0)
        ShowUsage();
      minmem = atol(argv[argindex]);
      if (minmem <= 0L || minmem > 16000000L)
      {
        sprintf(tstr, "Minimum memory level '%ld' out of range.", minmem);
        CleanUp(tstr);
      }
      argindex++;

      if (argc > argindex)
      {
        if (isdigit(argv[argindex][0]) == 0)
          ShowUsage();
        minfrag = atol(argv[argindex]);
        if (minfrag <= 0L || minfrag > 16000000L)
        {
          sprintf(tstr, "Minimum fragment size '%ld' out of range.", minfrag);
          CleanUp(tstr);
        }
        argindex++;

        if (argc > argindex)
        {
          if (isdigit(argv[argindex][0]) == 0)
            ShowUsage();
          maxfrag = atol(argv[argindex]);
          if (maxfrag <= 0L || maxfrag > 16000000L)
          {
            sprintf(tstr, "Maximum fragment size '%ld' out of range.", maxfrag);
            CleanUp(tstr);
          }
          if (maxfrag < minfrag)
          {
            sprintf(tstr, "Maximum fragment size '%ld' less than Minumum size '%ld'!", maxfrag, minfrag);
            CleanUp(tstr);
          }
        }
      }
    }
  }

/* Init RangeRand() seed */
  RattleDice();

/* Open control window */
  if ( (FragWin = OpenWindow(&NewWin)) == 0L)
    CleanUp("Can't open window!");

/* Feedback the post-parsed args, and give them time to read it */
  if (Verbose)
    printf("MinMemLevel: %ld MinFragSize: %ld MaxFragSize: %ld\n", minmem, minfrag, maxfrag);
  Delay(50L);

/* Perform thrash function using "minmem" as the lowest number of bytes that
 * we will allow before freeing a memory fragment, and using "maxfrag" as
 * the largest sized memory fragment to be randomly allocated
 */
  ThrashMem(minmem, minfrag, maxfrag);

/* All done, free everything */
  CleanUp("Done.");
}

/* -------------------------------------------------------------------- */
/* Play with random sized memory chunks, where:				*/
/*   minfrag <= random size <= maxfrag					*/
/* -------------------------------------------------------------------- */
VOID ThrashMem(minmem, minfrag, maxfrag)
  register LONG minmem, minfrag, maxfrag;
{
  register WORD done;

/* Allocate and initialize MinList of FragNode fragments */
  if ( (FragList = AllocMinList()) == 0L)
  {
    printf("No RAM for MinList structure?");
    CleanUp();
  }

/* Init variables, start thrash loop */
  done = 0;
  FailCount = AllocCount = TotalFragBytes = 0L;
  while (!done)
  {
/* Update ram statistics in TitleBar */
    PrintMemoryStats();

/* If available ram is greater than "minmem", attempt to allocate a random
 * sized fragment and add it to the MinList.  Otherwise, free an entry
 * picked at random from the linked-list of FragNodes.
 */
    if (AvailMem((LONG)MEMF_PUBLIC) < minmem)
    {
      (VOID)FreeRandomFrag(FragList);
    }
    else
    {
      (VOID)AddRandomFrag(FragList, minfrag, maxfrag);
    }

/* Check for exit conditions */
    done = GetIDCMPEvents(FragWin->UserPort);

/* Crudely give other tasks a chance to run */
    Delay(1L);
  }
}

/* -------------------------------------------------------------------- */
/* Show current fragmentation and memory statistics in FragWin window	*/
/* -------------------------------------------------------------------- */
VOID PrintMemoryStats()
{
  BYTE tstr[80];
  static BYTE fmtstr1[] = "Frags: %4ld  Fails: %4ld  Allocated: %7ld";
  static BYTE fmtstr2[] = "Type  Available   Largest";
  static BYTE fmtstr3[] = "chip    %7ld   %7ld";
  static BYTE fmtstr4[] = "fast    %7ld   %7ld";
  static BYTE fmtstr5[] = "total   %7ld   %7ld";
  LONG chipavail, chiplargest;
  LONG fastavail, fastlargest;
  LONG totalavail, abslargest;
  register struct RastPort *rp;

  Forbid();
  chipavail = AvailMem(MEMF_CHIP);
  chiplargest = AvailMem(MEMF_CHIP|MEMF_LARGEST);
  fastavail = AvailMem(MEMF_FAST);
  fastlargest = AvailMem(MEMF_FAST|MEMF_LARGEST);
  Permit();

  totalavail = chipavail + fastavail;
  if (chiplargest > fastlargest)
    abslargest = chiplargest;
  else
    abslargest = fastlargest;
  
  sprintf(tstr, fmtstr1, AllocCount, FailCount, TotalFragBytes);
  rp = FragWin->RPort;
  SetAPen(rp, 1L);
  Move(rp, 24L, 20L);
  Text(rp, tstr, (LONG)strlen(tstr));

  SetAPen(rp, 3L);
  Move(rp, 90L, 30L);
  Text(rp, fmtstr2, (LONG)strlen(fmtstr2));

  SetAPen(rp, 1L);
  sprintf(tstr, fmtstr3, chipavail, chiplargest);
  Move(rp, 90L, 40L);
  Text(rp, tstr, (LONG)strlen(tstr));

  sprintf(tstr, fmtstr4, fastavail, fastlargest);
  Move(rp, 90L, 50L);
  Text(rp, tstr, (LONG)strlen(tstr));

  sprintf(tstr, fmtstr5, totalavail, abslargest);
  Move(rp, 90L, 60L);
  Text(rp, tstr, (LONG)strlen(tstr));
}

/* -------------------------------------------------------------------- */
/* Given pointer to MinList and a maximum fragment size, allocate a	*/
/* FragNode with a fragment size minfrag <= size <= maxfrag and insert	*/
/* it at the head of the FragNode list.	Return actual size of fragment.	*/
/* -------------------------------------------------------------------- */
LONG AddRandomFrag(list, minfrag, maxfrag)
  struct MinList *list;
  LONG minfrag, maxfrag;
{
  register struct FragNode *tnode;
  register LONG tsize;

  if (minfrag == maxfrag)
    tsize = minfrag;
  else
    tsize = RangeRand(maxfrag - minfrag) + minfrag;
  tnode = AllocFragNode(tsize);			/* alloc a random sized node 	*/
  tsize += (LONG)sizeof(struct FragNode);	/* account for struct size! 	*/

  if (tnode != 0L)
  {
    AddHead(list, tnode);
    if (Verbose)
      printf("Adding a %7ld byte frag SUCCEEDED\n", tsize);
    AllocCount++;
    TotalFragBytes += tsize;
    return(tsize);
  }
  else
  {
    if (Verbose)
      printf("Adding a %7ld byte frag FAILED!\n", tsize);
    FailCount++;
    return(0L);
  }
}

/* -------------------------------------------------------------------- */
/* Given a pointer to a MinList of FragNodes, remove one FragNode	*/
/* at random from the list, deallocate it, and return the fragment	*/
/* size that was deallocated.  Return NULL (0L) if the list is empty.	*/
/* -------------------------------------------------------------------- */
LONG FreeRandomFrag(list)
  struct MinList *list;
{
  register LONG i, j;
  register struct FragNode *tnode;

/* Empty list! just return NULL */
  if (list->mlh_TailPred == list)
    return(0L);

/* Generate a random number N, such that 0L <= N <= 99L */
  j = RangeRand(100L);

/* Search through the MinList N times, restarting at lh_Head if we
 * hit the end of the list before finding the Nth node.
 */
  tnode = (struct FragNode *)list->mlh_Head;
  for (i = 0L; i < j; i++)
  {
    tnode = (struct FragNode *)tnode->fn_Node.mln_Succ;
    if (tnode == 0L)
      tnode = (struct FragNode *)list->mlh_Head;
  }

/* Remove this node from the list, grab it's size and deallocate */
  Remove(tnode);
  i = tnode->fn_Size + (LONG)sizeof(struct FragNode);
  if (Verbose)
    printf("Freeing  %7ld byte frag (0x%08lx)\n", i, tnode->fn_Data);
  FreeFragNode(tnode);
  AllocCount--;
  TotalFragBytes -= i;

/* Return the actual size of fragment deallocated */
  return(i);
}

/* -------------------------------------------------------------------- */
/* Given a IntuiMsgPort, process IDCMP messages received.  If we get	*/
/* a RAWKEY == RETURN, RAWKEY == ESCAPE, or CLOSEWINDOW event, return	*/
/* TRUE (1).  Else, return FALSE (0).
/* -------------------------------------------------------------------- */
WORD GetIDCMPEvents(userport)
  register struct IntuiMessage *userport;
{
  register struct IntuiMessage *imsg;
  register ULONG class;
  register UWORD code;
  register UWORD qualifier;
  register WORD retval;

  retval = 0;
  while ( (imsg = (struct IntuiMessage *)GetMsg(userport)) != 0L)
  {
    class = imsg->Class;
    code = imsg->Code;
    qualifier = imsg->Qualifier;
    ReplyMsg(imsg);

    switch (class)
    {
      case RAWKEY:
        if (code == 0xc4 || code == 0xc5)
          retval = 1;
        break;
      case CLOSEWINDOW:
        retval = 1;
        break;
    }

  }
  return (retval);
}

/* -------------------------------------------------------------------- */
/* Allocate and initialize a MinList structure, return pointer to same.	*/
/* -------------------------------------------------------------------- */
struct MinList *AllocMinList()
{
  register struct MinList *mlist;

  mlist = (struct MinList *)AllocMem((LONG)sizeof(struct MinList), MEMF_PUBLIC);
  if (mlist)
    NewList(mlist);
  return(mlist);
}

/* -------------------------------------------------------------------- */
/* Free memory allocated to a MinList structure, if allocated.		*/
/* -------------------------------------------------------------------- */
VOID FreeMinList(list)
  struct MinList *list;
{
  if (list)
    FreeMem(list, (LONG)sizeof(struct MinList));
}

/* -------------------------------------------------------------------- */
/* Allocate and initialize a FragNode structure, return pointer or 0L	*/
/* -------------------------------------------------------------------- */
struct FragNode *AllocFragNode(datasize)
  LONG datasize;
{
  register struct FragNode *mnode;

  mnode = (struct FragNode *)AllocMem(datasize + (LONG)sizeof(struct FragNode), MEMF_PUBLIC);
  if (mnode != 0L)
  {
    mnode->fn_Size = datasize;
    mnode->fn_Data = (BYTE *)((LONG)mnode + (LONG)sizeof(struct FragNode));
  }
  return(mnode);
}

/* -------------------------------------------------------------------- */
/* Free memory allocated to a FragNode structure, if allocated.		*/
/* -------------------------------------------------------------------- */
VOID FreeFragNode(mnode)
  struct FragNode *mnode;
{
  if (mnode)
    FreeMem(mnode, (LONG)sizeof(struct FragNode) + mnode->fn_Size);
}

/* -------------------------------------------------------------------- */
/* Walk list of allocated FragNodes in a MinList and free each one,	*/
/* then deallocate the MinList itself. 					*/
/* -------------------------------------------------------------------- */
VOID FreeAllFragNodes(list)
  struct List *list;
{
  register struct FragNode *tnode;

  if (list)
  {
    while ( (tnode = (struct FragNode *)RemHead(list)) != 0L)
      FreeFragNode(tnode);
    FreeMinList(list);
  }
}

/* -------------------------------------------------------------------- */
/* If CLI, print usage and exit.  If Workbench, exit cleanly.		*/
/* -------------------------------------------------------------------- */
VOID ShowUsage()
{
  CleanUp(\
"Version 1.0 - Dynamic memory thrasher.\n\
Copyright \xa9 1988 by Justin V. McCormick - Freely Redistributable\n\n\
\x9b1;33;40mUsage:\x9b0m Fragit [-verbose] [MinMemLevel] [MinFragSize] [MaxFragSize]\n\
        Default Parameters: (100000)        (12)         (10000)\n" );

}

/* -------------------------------------------------------------------- */
/* Universal exit point for entire program.				*/
/* -------------------------------------------------------------------- */
VOID CleanUp(exitmsg)
  BYTE *exitmsg;
{
  if (exitmsg && Output() != 0L)
    printf("\x9b1;33;40mFragit:\x9b0m %s\n", exitmsg);

  FreeAllFragNodes(FragList);
  if (FragWin)
    CloseWindow(FragWin);
  if (IntuitionBase)
    CloseLibrary(IntuitionBase);
  if (GfxBase)
    CloseLibrary(GfxBase);
  exit(0);
}

/* -------------------------------------------------------------------- */
/* Attempt to make RangeRand() a little less predictable between runs.	*/
/* -------------------------------------------------------------------- */
VOID RattleDice()
{
  struct DateStamp stime;
  register WORD i, j;
  register LONG seedval;

  DateStamp(&stime);
  seedval = stime.ds_Tick;
  j = (WORD)RangeRand(seedval + 0xaa) & 0xff;
  for (i = 0; i < j; i++)
    (VOID)RangeRand(seedval);
}
