
#INCLUDE "course.module"
#INCLUDE "sdlraster.module"
#INCLUDE "rastergraphics.module"
#INCLUDE "rastertext.module"
#INCLUDE "file.module"
#INCLUDE "string.module"
#INCLUDE "convert.module"
#INCLUDE "useful.module"

--==============================================================================================
--                    TODO
--
--  *) used shared data for drawing and receiving data
--  *) transmission of data in reals, not in ints
--  *) used memory, rather than free memory.
--  *) create memory muncher program
--  *) create processor muncher program
--==============================================================================================

PROC main (CHAN BYTE in?, SHARED CHAN BYTE out!, CHAN BYTE err!)

  VAL INT display.height IS 650 :
  VAL INT display.width IS 900 :
  VAL INT stored.values IS 100 :
  VAL INT frame.rate IS INT 60 :
  VAL INT read.rate IS INT 10 :

  --{{{ raster colour definitions
  VAL INT COL.BLACK IS        #000000 :
  VAL INT COL.WHITE IS        #AFAFAF :
  VAL INT COL.GREY IS         #6F6F6F :
  VAL INT COL.DARK.GREY IS    #3F3F3F :
  VAL INT COL.VERY.DARK.GREY IS #1F1F1F :
  VAL INT COL.MED.GREY IS     #6F6F6F :
  VAL INT COL.BLUE IS         #0000FF :
  VAL INT COL.RED IS          #FF0000 :
  VAL INT COL.GREEN IS        #00FF00 :
  VAL INT COL.DARK.YELLOW IS  #303000 :
  VAL INT COL.DARK.PURPLE IS  #330030 :
  --}}}
   
  -- for storing state of cpu activity at one point in time
  DATA TYPE CPU.UTILIZATION.DT
    RECORD
      INT cpu :             -- total
      INT cpu0 :            -- cpu 0 stats
      INT cpu1 :            -- cpu 1 stats
  :

  -- for storing a history of cpu utilization activity
  DATA TYPE CPU.UTILIZATION.DATA.DT
    RECORD
      [stored.values]INT cpu.data :
      [stored.values]INT cpu0.data :
      [stored.values]INT cpu1.data :
  :

  -- this data is not stored over a period of time. 
  DATA TYPE CPU.LOADAVG.DT
    RECORD
      REAL32 min1 :
      REAL32 min5 :
      REAL32 min15 :
  :

  DATA TYPE CPU.CLOCKSPD.DT
    RECORD
      REAL32 cpu0 :
      REAL32 cpu1 :
  :

  DATA TYPE MEMORY.UTILIZATION.DT
    RECORD
      INT max :
      INT free :
      INT used :
  :

  DATA TYPE MEMORY.UTILIZATION.DATA.DT
    RECORD
      [stored.values]INT max :
      [stored.values]INT free :
      [stored.values]INT used :
  :


  CHAN TYPE SYSTEM.DATA.TO.DATASTORE.CT
    MOBILE RECORD 
      CHAN CPU.UTILIZATION.DT cpu? :                -- server end would receive data (hence the specifier)
      CHAN CPU.LOADAVG.DT cpu.loadavg? :            -- server end would receive data (hence the specifier)
      CHAN CPU.CLOCKSPD.DT cpu.clockspd? :
      CHAN MEMORY.UTILIZATION.DT memory? :          -- server end would receive data (hence the specifier)   
  :


  CHAN TYPE DATA.REQ.CPU.UTILIZATION.CT
    MOBILE RECORD
      CHAN INT req? :
      CHAN CPU.UTILIZATION.DATA.DT data! :
  :

  CHAN TYPE DATA.REQ.CPU.LOADAVG.CT
    MOBILE RECORD 
      CHAN INT req? :
      CHAN CPU.LOADAVG.DT data! :
  :

  CHAN TYPE DATA.REQ.CPU.CLOCKSPD.CT
    MOBILE RECORD
      CHAN INT req? :
      CHAN CPU.CLOCKSPD.DT data! :
  :

  CHAN TYPE DATA.REQ.MEMORY.UTILIZATION.CT      -- recall that specifiers are from server's pov (data.store)
    MOBILE RECORD
      CHAN INT req? :                            -- request data
      CHAN MEMORY.UTILIZATION.DATA.DT data! :
  :

  --{{{ CHAN TYPE SYSTEM.DATA.FROM.DATASTORE.CT this is broken, waiting for a fix from Adam
--  CHAN TYPE SYSTEM.DATA.FROM.DATASTORE.CT             -- data.store will still be the server end, because it receives requests
--    MOBILE RECORD 
--      CHAN DATA.REQ.CPU.UTILIZATION.CT cpu? :
--      CHAN DATA.REQ.MEMORY.UTILIZATION.CT memory? :
--  :
  --}}}

  CHAN SYSTEM.DATA.TO.DATASTORE.CT system.data.communication :


  --{{{ utilities
  --{{{ INLINE PROC print.result
  --  prints the result of performing an operation on a file to the terminal
  INLINE PROC print.result(VAL []BYTE file.name, VAL []BYTE operation, INT result)
    CLAIM out!
      SEQ
        out.string("*nresult of ", 0, out!) 
        out.string(operation, 0, out!)
        out.string(" the file ", 0, out!)
        out.string(file.name, 0, out!)
        out.string(" was : ", 0, out!)
        out.int   (result, 0, out!)
        out.string("*n", 0, out!)
  :
  --}}}

  INLINE PROC print.string.mob(MOBILE []BYTE s)
    CLAIM out!
      SEQ
        out.string(s, 0, out!)
        out.string("*n", 0, out!)
  :
  
  INLINE PROC print.string.val(VAL []BYTE s)
    CLAIM out!
      SEQ
        out.string(s, 0, out!)
        out.string("*n", 0, out!)
  :

  INLINE PROC print.string([]BYTE s)
    CLAIM out!
      SEQ
        out.string(s, 0, out!)
        out.string("*n", 0, out!)
  :
  
  INLINE PROC print.int (INT i)
    CLAIM out!
      SEQ
        out.int (i, 0, out!)
        out.string("*n", 0, out!)
  :
  
  INLINE PROC print.int.val(VAL INT i)
    CLAIM out!
      SEQ 
        out.int (i, 0, out!)
        out.string("*n", 0, out!)
  :

  PROC print.coordinate (VAL INT x, y)
    CLAIM out!
      SEQ
        out.string("x : ", 0, out!)
        out.int (x, 0, out!)
        out.string("      y : ", 0, out!)
        out.int(y, 0, out!)
        out.string("*n", 0, out!)
  :

  PROC remove.prefix.spaces (MOBILE []BYTE in, RESULT MOBILE []BYTE out)
    INITIAL BOOL found.nonspace IS FALSE :
    INITIAL INT i IS 0 :

    SEQ
      WHILE (NOT found.nonspace) OR (i = (SIZE in))
        IF
          in[i] = #20           -- ascii code for space
            i := i + 1          -- examine next character
          TRUE                  -- nonspace found
            found.nonspace := TRUE
      out := [in FROM i]    
  :
  --}}}



  --{{{ PROC read.file.system
  PROC read.file.system (SYSTEM.DATA.TO.DATASTORE.CT! sys.data.to.datastore.ch)
 
    --{{{ PROC read.timer
    -- process controls how frequently files are read, uses a barrier to do so. 
    PROC read.timer (BARRIER read.barrier)
      VAL INT read.delay IS (1000000 / read.rate) :
      TIMER timer :
      INT time.now :

      WHILE TRUE
        SEQ
          SYNC read.barrier
          timer ? time.now
          timer ? AFTER time.now PLUS read.delay
    :
    --}}}

    --{{{ PROC read.loadavg
    PROC read.loadavg (CHAN CPU.LOADAVG.DT cpu.loadavg.ch!, BARRIER read.barrier)
      --{{{ PROC get.loadavg 
      PROC get.loadavg (RESULT CPU.LOADAVG.DT loadavg.packet)
        VAL []BYTE loadavg.filename IS "/proc/loadavg" :
        INT loadavg.fd, mode, result :
        [100]BYTE buffer :
        MOBILE []BYTE buffer.rest, buffer.temp, buffer.data :
        INT start.char :
        BOOL temp :
      
        SEQ
          mode := 0 \/ O.RDONLY
          file.open (loadavg.filename, mode, loadavg.fd)
          file.read (loadavg.fd, buffer, result)
          file.close (loadavg.fd, result)

          --{{{ retrieve 1min
          -- format of data in loadavg is 'xx.xx xx.xx xx.xx'          
          start.char := string.pos (" ", buffer)      -- the first space is after the first number
          buffer.data := [buffer FOR start.char]
          STRINGTOREAL32 (temp, loadavg.packet[min1], buffer.data)
          buffer.rest := [buffer FROM (start.char + 1)]
          --}}}
          --{{{retrieve 5min
          start.char := string.pos (" ", buffer.rest)
          buffer.data := [buffer.rest FOR start.char]
          STRINGTOREAL32 (temp, loadavg.packet[min5], buffer.data)          
          buffer.temp := [buffer.rest FROM (start.char + 1)]
          buffer.rest := buffer.temp
          --}}}
          --{{{retrieve 15min
          start.char := string.pos (" ", buffer.rest)
          buffer.data := [buffer.rest FOR start.char]
          STRINGTOREAL32 (temp, loadavg.packet[min15], buffer.data)          
          buffer.temp := [buffer.rest FROM start.char]
          buffer.rest := buffer.temp
          --}}}

      :
      --}}}      
     
      CPU.LOADAVG.DT loadavg.packet :
      WHILE TRUE
        SEQ
          SYNC read.barrier
          get.loadavg (loadavg.packet)
          cpu.loadavg.ch ! loadavg.packet
    :
    --}}}

    --{{{ PROC read.cpuinfo
    PROC read.cpuinfo (CHAN CPU.CLOCKSPD.DT cpu.clockspd.ch!, BARRIER read.barrier)
      -- PROC get.cpuinfo
      PROC get.cpuinfo (RESULT CPU.CLOCKSPD.DT clockspd.packet)
        VAL []BYTE cpuinfo.filename IS "/proc/cpuinfo" :
        VAL []BYTE cpuMhz.tag IS "cpu MHz" :
        VAL INT cpuMhz.tag.size IS 11 :         -- as derived from the cpuinfo file output, rather than from the string above. 
        INT cpuinfo.fd, mode, result :
        [3000]BYTE buffer :
        MOBILE []BYTE buffer.rest, buffer.temp, buffer.data :
        INT start.char :
        BOOL temp :
        MOBILE []BYTE temp.s :
        

        SEQ 
          mode := 0 \/ O.RDONLY
          file.open (cpuinfo.filename, mode, cpuinfo.fd)
          file.read (cpuinfo.fd, buffer, result)
  
          start.char := (string.pos (cpuMhz.tag, buffer) + cpuMhz.tag.size)
          buffer.rest := [buffer FROM start.char]
          start.char := string.pos ("*n", buffer.rest)
          buffer.data := [buffer.rest FOR start.char]
          STRINGTOREAL32 (temp, clockspd.packet[cpu0], buffer.data)
          CLAIM out!
            SEQ
              print.s(buffer.data, out!)
              print.s("_", out!)
              
          start.char := (string.pos (cpuMhz.tag, buffer) + cpuMhz.tag.size)
          buffer.rest := [buffer FROM start.char]
          start.char := string.pos ("*n", buffer.rest)
          buffer.data := [buffer.rest FOR start.char]
          STRINGTOREAL32 (temp, clockspd.packet[cpu1], buffer.data)  
          temp.s := MOBILE [15]BYTE  
          REAL32TOSTRING (result, temp.s, clockspd.packet[cpu1], 5, 5) 

          CLAIM out!
            SEQ
              print.s(buffer.data, out!)
              print.s(" !   ", out!)
              print.s(temp.s, out!) 
              IF 
                temp = TRUE
                  print.s("    true, BAD conversion", out!)
                    
                TRUE
                  print.s("    false, GOOD conversion", out!)
              print.sn(" ", out!)
      :
      --}}}
      CPU.CLOCKSPD.DT clockspd.packet :
      WHILE TRUE
        SEQ
          SYNC read.barrier
          get.cpuinfo (clockspd.packet)
          
    :
    --}}}

    --{{{ PROC read.meminfo
    PROC read.meminfo (CHAN MEMORY.UTILIZATION.DT memory.info.ch!, BARRIER read.barrier)
      --{{{ PROC get.meminfo
      PROC get.meminfo (RESULT MEMORY.UTILIZATION.DT memory.data.packet)
        VAL []BYTE meminfo.filename IS "/proc/meminfo" :
        INT meminfo.fd, mode, result :
        [3000]BYTE buffer :
        MOBILE []BYTE buffer.rest, buffer.temp, buffer.data :
        INT start.char :
        VAL []BYTE memtotal.tag IS "MemTotal:" :
        VAL []BYTE memfree.tag IS "MemFree:" :
        BOOL temp :

        SEQ
          mode := 0 \/ O.RDONLY
          file.open (meminfo.filename, mode, meminfo.fd)
          file.read (meminfo.fd, buffer, result)
          file.close (meminfo.fd, result)
          --{{{ retrieve MemoryTotal
          start.char := string.pos (memtotal.tag, buffer)          
          start.char := start.char + (SIZE memtotal.tag)
          buffer.rest := [buffer FROM start.char]
          remove.prefix.spaces (buffer.rest, buffer.temp)
          buffer.rest := buffer.temp
          start.char := string.pos (" ", buffer.rest)
          buffer.data := [buffer.rest FOR start.char]
          STRINGTOINT (temp, memory.data.packet[max], buffer.data)
          --}}}
          
          --{{{ retrieve MemoryFree
          start.char := string.pos (memfree.tag, buffer)          
          start.char := start.char + (SIZE memfree.tag)
          buffer.rest := [buffer FROM start.char]
          remove.prefix.spaces (buffer.rest, buffer.temp)
          buffer.rest := buffer.temp
          start.char := string.pos (" ", buffer.rest)
          buffer.data := [buffer.rest FOR start.char]
          STRINGTOINT (temp, memory.data.packet[free], buffer.data)          
          --}}}
          
          -- caculate memory used
          memory.data.packet[used] := memory.data.packet[max] - memory.data.packet[free]
      :
      --}}}
      MEMORY.UTILIZATION.DT memory.data.packet :      
      WHILE TRUE
        SEQ
          SYNC read.barrier
          get.meminfo (memory.data.packet)
          memory.info.ch ! memory.data.packet
    :
    --}}}

    --{{{ PROC read.stat
    --  process retreives information from the /proc/stat file. CPU utilization (in percentage) is calculated, and it output down
    --  the corresponding channel. 
    PROC read.stat (CHAN CPU.UTILIZATION.DT cpu.info.ch!, BARRIER read.barrier)

      DATA TYPE STAT.CPU.DATA.DT
        RECORD
          INT user :
          INT nice :
          INT system :
          INT idle :
      :  

      --{{{ PROC get.stats
      PROC get.stats(RESULT STAT.CPU.DATA.DT cpu.stats, cpu0.stats, cpu1.stats)
        VAL INT buffer.size IS 3000 :

        --{{{ PROC retrieve.cpu.stats.from.file
        PROC retrieve.cpu.stats.from.file ([buffer.size]BYTE buffer, VAL []BYTE cpu.tag, RESULT STAT.CPU.DATA.DT cpu.stats)
          MOBILE []BYTE buffer.rest, buffer.temp, buffer.data :
          INT start.char, start.space :
          BOOL temp :

          SEQ
            -- extracting the 'user' time
            start.char := string.pos (cpu.tag, buffer)            -- find start of cpu.tag
            start.char := start.char + (SIZE cpu.tag)             -- find end of cpu.tag
            buffer.temp := [buffer FROM start.char]               -- remove cpu.tag
            remove.prefix.spaces (buffer.temp, buffer.rest)       -- remove spaces in front of digits  
            start.space := string.pos (" ", buffer.rest)          -- find next space (delineates end of number)
            buffer.data := [buffer.rest FOR start.space]          -- extract the number
            STRINGTOINT (temp, cpu.stats[user], buffer.data)      -- store the number
    
            -- extracting the 'nice' time
            start.space := string.pos (" ", buffer.rest) + 1      -- find the start of the next number
            buffer.rest := [buffer.rest FROM start.space]         -- remove the space
            start.space := string.pos (" ", buffer.rest)          -- find the end of next number
            buffer.data := [buffer.rest FOR start.space]          -- extract the number
            STRINGTOINT (temp, cpu.stats[nice], buffer.data)      -- store the number
    
            -- extracting the 'system' time
            start.space := string.pos (" ", buffer.rest) + 1      -- find the start of the next number
            buffer.rest := [buffer.rest FROM start.space]         -- remove the preceeding spaces
            start.space := string.pos (" ", buffer.rest)          -- find the end of the next number
            buffer.data := [buffer.rest FOR start.space]          -- extract the number
            STRINGTOINT (temp, cpu.stats[system], buffer.data)    -- store the number
  
            -- extracting the 'idle' time
            start.space := string.pos (" ", buffer.rest) + 1      -- find the start of the next number
            buffer.rest := [buffer.rest FROM start.space]         -- remove the preceeding space
            start.space := string.pos (" ", buffer.rest)          -- find the end of the next number
            buffer.data := [buffer.rest FOR start.space]          -- extract the number
            STRINGTOINT (temp, cpu.stats[idle], buffer.data)      -- store the number
        :
        --}}}

        VAL []BYTE stat.filename IS "/proc/stat" :
        INT stat.fd, mode, result :
        [buffer.size]BYTE buffer :
        VAL []BYTE cpu.tag IS "cpu" :
        VAL []BYTE cpu0.tag IS "cpu0" :
        VAL []BYTE cpu1.tag IS "cpu1" :

        SEQ 
          mode := 0 \/ O.RDONLY                                           -- set up read mode
          file.open (stat.filename, mode, stat.fd)                        -- open the file
          file.read (stat.fd, buffer, result)                             -- read the file
          file.close (stat.fd, result)                                    -- close file

          retrieve.cpu.stats.from.file (buffer, cpu.tag, cpu.stats)       -- extract data
          retrieve.cpu.stats.from.file (buffer, cpu0.tag, cpu0.stats)
          retrieve.cpu.stats.from.file (buffer, cpu1.tag, cpu1.stats)
      :
      --}}}

      --{{{ PROC calculate.cpu.utilization
      PROC calculate.cpu.utilization (STAT.CPU.DATA.DT cpu.last.read, cpu.new.read, RESULT INT utilization)
        STAT.CPU.DATA.DT difference : 
        INT cpu.work, cpu.total.time :         

        SEQ
          difference[user] := cpu.new.read[user] - cpu.last.read[user]
          difference[nice] := cpu.new.read[nice] - cpu.last.read[nice]
          difference[system] := cpu.new.read[system] - cpu.last.read[system]
          difference[idle] := cpu.new.read[idle] - cpu.last.read[idle]

          cpu.work := difference[user] + difference[nice]
          cpu.work := cpu.work + difference[system]            
          cpu.total.time := cpu.work + difference[idle] 
          
          IF
            cpu.work <> 0                     -- protection from divide by zero
              utilization := (cpu.work * 100) / cpu.total.time        -- multiply to get percentage
            TRUE
              utilization := 0
      :
      --}}}

      STAT.CPU.DATA.DT cpu.last.read, cpu.new.read :
      STAT.CPU.DATA.DT cpu0.last.read, cpu0.new.read :
      STAT.CPU.DATA.DT cpu1.last.read, cpu1.new.read :
      CPU.UTILIZATION.DT cpu.data.packet :

      SEQ
        get.stats (cpu.last.read, cpu0.last.read, cpu1.last.read)                 -- get initial baseline stats
        WHILE TRUE
          SEQ
            SYNC read.barrier
            get.stats (cpu.new.read, cpu0.new.read, cpu1.new.read)                -- retrieve stats from file. 
      
            calculate.cpu.utilization (cpu.last.read, cpu.new.read, cpu.data.packet[cpu])
            calculate.cpu.utilization (cpu0.last.read, cpu0.new.read, cpu.data.packet[cpu0])
            calculate.cpu.utilization (cpu1.last.read, cpu1.new.read, cpu.data.packet[cpu1])

            cpu.last.read := cpu.new.read                     -- swap values, new becomes old.
            cpu0.last.read := cpu0.new.read
            cpu1.last.read := cpu1.new.read

            cpu.info.ch ! cpu.data.packet
    :
    --}}}
    
    BARRIER read.barrier :
    PAR ENROLL read.barrier
      read.timer (read.barrier)
      read.stat (sys.data.to.datastore.ch[cpu]?, read.barrier)
      read.loadavg (sys.data.to.datastore.ch[cpu.loadavg], read.barrier)
      read.cpuinfo (sys.data.to.datastore.ch[cpu.clockspd], read.barrier)
      read.meminfo (sys.data.to.datastore.ch[memory]?, read.barrier)
  :
  --}}}


  --{{{ PROC data.store
  PROC data.store (SYSTEM.DATA.TO.DATASTORE.CT? sys.data.from.file.ch,  
                    DATA.REQ.CPU.UTILIZATION.CT? cpu.data.to.display.ch,
                    DATA.REQ.CPU.LOADAVG.CT? cpu.loadavg.data.to.display.ch,
                    DATA.REQ.CPU.CLOCKSPD.CT? cpu.clockspd.data.to.display.ch,
                    DATA.REQ.MEMORY.UTILIZATION.CT? memory.data.to.display.ch )
                   
    
    --{{{ PROC cpu.data.store
    PROC cpu.data.store (CHAN CPU.UTILIZATION.DT cpu.data.from.file.ch?, DATA.REQ.CPU.UTILIZATION.CT? cpu.data.to.display.ch)  
      [stored.values] INT cpu.data :
      [stored.values] INT cpu0.data :
      [stored.values] INT cpu1.data :
      CPU.UTILIZATION.DT cpu.data.packet :
      INT temp :
  
      SEQ
        SEQ i = 0 FOR stored.values               -- clear array values to 0. 
          SEQ
            cpu.data[i] := 0
            cpu0.data[i] := 0 
            cpu1.data[i] := 0
        WHILE TRUE
          PRI ALT                                 -- deal with request from display before input from filesystem. Helps combat flickering. 
            cpu.data.to.display.ch[req] ? temp
              cpu.data.to.display.ch[data] ! [cpu.data, cpu0.data, cpu1.data] (CPU.UTILIZATION.DATA.DT)

            cpu.data.from.file.ch ? cpu.data.packet           -- receive data from file read
              SEQ
                --{{{ maintain array of percentage values (shift and insert)
                SEQ i = (stored.values - 1) FOR (stored.values - 1) STEP -1           -- will cycle from stored.values (inclusive) to 1 (inclusive).  
                  SEQ                   
                    cpu.data[i] := cpu.data[i - 1]      -- shift everything along one.
                    cpu0.data[i] := cpu0.data[i - 1]    -- shift everything along one.
                    cpu1.data[i] := cpu1.data[i - 1]    -- shift everything along one.
                cpu.data[0] := cpu.data.packet[cpu]     -- cpu.data[0] holds more recent value
                cpu0.data[0] := cpu.data.packet[cpu0]   -- cpu.data[0] holds more recent value
                cpu1.data[0] := cpu.data.packet[cpu1]   -- cpu.data[0] holds more recent value
                --}}}  
  

    :
    --}}}

    --{{{ PROC cpu.loadavg.data.store
    PROC cpu.loadavg.data.store (CHAN CPU.LOADAVG.DT cpu.loadavg.from.file.ch?, DATA.REQ.CPU.LOADAVG.CT? cpu.loadavg.data.to.display.ch)
      INITIAL CPU.LOADAVG.DT cpu.loadavg IS [0.0, 0.0, 0.0] :
      INT temp :      

      WHILE TRUE
        PRI ALT         -- give priority to writing to display, counter flicker.
          cpu.loadavg.data.to.display.ch[req] ? temp
            cpu.loadavg.data.to.display.ch[data] ! cpu.loadavg

          cpu.loadavg.from.file.ch ? cpu.loadavg
            SKIP        -- nothing to do here.  

    :
    --}}}
    
    --{{{ PROC cpu.clockspd.data.store
    PROC cpu.clockspd.data.store (CHAN CPU.CLOCKSPD.DT cpu.clockspd.from.file.ch?, DATA.REQ.CPU.CLOCKSPD.CT? cpu.clockspd.to.display.ch )
      INITIAL CPU.CLOCKSPD.DT cpu.clockspd IS [0.0, 0.0] :
      INT temp :

      WHILE TRUE
        PRI ALT
          cpu.clockspd.to.display.ch[req] ? temp
            cpu.clockspd.to.display.ch[data] ! cpu.clockspd

          cpu.clockspd.from.file.ch ? cpu.clockspd
            SKIP
    :
    --}}}

    --{{{ PROC memory.data.store
    PROC memory.data.store (CHAN MEMORY.UTILIZATION.DT memory.data.from.file.ch?, DATA.REQ.MEMORY.UTILIZATION.CT? memory.data.to.display.ch)  
      [stored.values]INT memory.max.data :
      [stored.values]INT memory.free.data :
      [stored.values]INT memory.used.data :

      MEMORY.UTILIZATION.DT memory.data.packet :
      INT temp :
  
      SEQ
        SEQ i = 0 FOR stored.values               -- clear array values to 0. 
          SEQ
            memory.max.data[i] := 0
            memory.free.data[i] := 0 
            memory.used.data[i] := 0
        WHILE TRUE
          ALT 
            memory.data.from.file.ch ? memory.data.packet           -- receive data from file read
              SEQ
                --{{{ maintain array of percentage values (shift and insert)
                SEQ i = (stored.values - 1) FOR (stored.values - 1) STEP -1           -- will cycle from stored.values (inclusive) to 1 (inclusive).  
                  SEQ                   
                    memory.max.data[i] := memory.max.data[i - 1]      -- shift everything along one.
                    memory.free.data[i] := memory.free.data[i - 1]    -- shift everything along one.
                    memory.used.data[i] := memory.used.data[i - 1]
                memory.max.data[0] := memory.data.packet[max]     -- cpu.data[0] holds more recent value
                memory.free.data[0] := memory.data.packet[free]   -- cpu.data[0] holds more recent value
                memory.used.data[0] := memory.data.packet[used]
                --}}}  
  
            memory.data.to.display.ch[req] ? temp
              memory.data.to.display.ch[data] ! [memory.max.data, memory.free.data, memory.used.data] (MEMORY.UTILIZATION.DATA.DT)
    :
    --}}}

    --{{{ main body of 'data.store'
    PAR
      cpu.data.store (sys.data.from.file.ch[cpu], cpu.data.to.display.ch)
      cpu.loadavg.data.store (sys.data.from.file.ch[cpu.loadavg], cpu.loadavg.data.to.display.ch)
      cpu.clockspd.data.store (sys.data.from.file.ch[cpu.clockspd], cpu.clockspd.data.to.display.ch)
      memory.data.store (sys.data.from.file.ch[memory], memory.data.to.display.ch)      
    --}}}      
  :
  --}}}


  --{{{ PROC display
  PROC display (CHAN RASTER r.in?, r.out!, 
                    DATA.REQ.CPU.UTILIZATION.CT! cpu.data.from.datastore.ch,
                    DATA.REQ.CPU.LOADAVG.CT! cpu.loadavg.data.ch,
                    DATA.REQ.CPU.CLOCKSPD.CT! cpu.clockspd.data.ch,
                    DATA.REQ.MEMORY.UTILIZATION.CT! memory.data.from.datastore.ch )

    VAL INT stored.values IS 100 :
    VAL INT box.height IS 100 :

    --{{{ INT FUNCTION scale.pixel.height
    INT FUNCTION scale.pixel.height (VAL INT data, VAL INT data.max, VAL INT range)
      INT temp :
      VALOF
        SEQ
          temp := (data * range) / data.max              -- scale pixel
        RESULT temp
    :
    --}}}
    --{{{ INT FUNCTION scale.pixel.width
    INT FUNCTION scale.pixel.width (VAL INT data, VAL INT data.items, VAL INT width)
      INT temp :
      VALOF
        SEQ
          temp := (width / data.items) * data
        RESULT temp
    :
    --}}}

    --{{{ PROC connect.pixels.with.line
    --  this process draws a line on a raster between two data points, correctly placing them within a rectange, who's dimensions are given.
    --  process is intended for use in drawing time-value graphs of CPU (and similar) data. 
    PROC connect.pixels.with.line (RASTER r, VAL INT data.x1, data.y1, data.x2, data.y2, VAL INT data.max.value, 
                                             VAL INT box.height, box.width, box.bottom, box.side, VAL INT colour)
      INT y1, x1, y2, x2, dx, dy :
      SEQ
        y1 := scale.pixel.height (data.y1, data.max.value, box.height)
        y1 := box.bottom - y1           -- calculate correct position of pixel on raster                 
        x1 := scale.pixel.width (data.x1, stored.values, box.width)
        x1 := x1 + box.side               
        y2 := scale.pixel.height (data.y2, data.max.value, box.height)
        y2 := box.bottom - y2
        x2 := scale.pixel.width (data.x2, stored.values, box.width)
        x2 := x2 + box.side 
        dx := x2 - x1
        dy := y2 - y1
        draw.line (x1, y1, dx, dy, #FFFFFF, colour, r)
    :
    --}}}

    --{{{ PROC clear.display
    PROC clear.display (CHAN RASTER r.in?, r.out!)
      RASTER r :
      WHILE TRUE
        SEQ
          r.in? r
          raster.clear (r, COL.BLACK)
          r.out! r
    :
    --}}}

    --{{{ PROC frame.rate
    -- process controls the rate at which frames are drawn on the display.
    PROC frame.rate (CHAN RASTER r.in?, r.out!)
      TIMER timer :
      INT time.now :
      VAL INT frame.delay IS (1000000 / frame.rate) :  
      RASTER r :  

      WHILE TRUE
        SEQ
          r.in ? r
          --{{{ delay passing of frame for a certain time
          timer ? time.now
          timer ? AFTER time.now PLUS frame.delay
          --}}}
          r.out ! r
    :
    --}}}
    
    --{{{ PROC draw.cpu
    PROC draw.cpu (CHAN RASTER r.to.draw.cpu?, r.from.draw.cpu!, DATA.REQ.CPU.UTILIZATION.CT! cpu.data.ch, 
                                                                 DATA.REQ.CPU.LOADAVG.CT! cpu.loadavg.data.ch,
                                                                 DATA.REQ.CPU.CLOCKSPD.CT! cpu.clockspd.data.ch)
      RASTER r, r.out :
      VAL INT CPU.BOX.WIDTH IS 400 :
      VAL INT CPU.BOX.HEIGHT IS 200 :
      VAL INT CPU.BOX.BOTTOM IS 500 :
      VAL INT CPU.BOX.TOP IS (CPU.BOX.BOTTOM - CPU.BOX.HEIGHT) :
      VAL INT CPU.BOX.SIDE IS 100 :
      VAL INT CPU.BOX.RIGHT.SIDE IS CPU.BOX.SIDE + CPU.BOX.WIDTH :

      VAL INT CPU.KEY.LINE.LENGTH IS 30 : 
      VAL INT CPU.KEY.LINE.LEFT.SIDE IS (CPU.BOX.RIGHT.SIDE + 25) :
      VAL INT CPU.KEY.TEXT.LEFT.SIDE IS ((CPU.KEY.LINE.LEFT.SIDE + CPU.KEY.LINE.LENGTH) + 10) :
      VAL INT CPU.KEY.HEIGHT IS (CPU.BOX.TOP + 40) :
      VAL INT CPU0.KEY.HEIGHT IS (CPU.KEY.HEIGHT + 30) :
      VAL INT CPU1.KEY.HEIGHT IS (CPU0.KEY.HEIGHT + 100) :

 
      --{{{ PROC draw.cpu.loadavg
      PROC draw.cpu.loadavg (RASTER r.in, RESULT RASTER r.out, DATA.REQ.CPU.LOADAVG.CT! cpu.loadavg.data.ch)
        VAL INT LOADAVG.KEY.TOP IS CPU.BOX.BOTTOM + 30 :
        VAL INT LOADAVG.KEY.MIN1 IS CPU.BOX.SIDE + 80 :
        VAL INT LOADAVG.KEY.MIN5 IS CPU.BOX.SIDE + 140 :
        VAL INT LOADAVG.KEY.MIN15 IS CPU.BOX.SIDE + 200 :
        VAL INT LOADAVG.BOX.TOP IS CPU.BOX.BOTTOM + 50 :
        VAL INT LOADAVG.BOX.LEFT.SIDE IS CPU.BOX.SIDE :
        VAL INT LOADAVG.TEXT.COL.KEY IS COL.DARK.GREY :
        VAL INT LOADAVG.TEXT.COL.VAR IS COL.GREY :
        CPU.LOADAVG.DT cpu.loadavg.packet :
        [6]BYTE min1, min5, min15 :
        INITIAL INT size IS SIZE min1 :
        

        SEQ            
          cpu.loadavg.data.ch[req] ! 1                                  -- actual value sent here is irrelevant
          cpu.loadavg.data.ch[data] ? cpu.loadavg.packet 

          REAL32TOSTRING(size, min1, cpu.loadavg.packet[min1], 2, 2)    -- convert REAL32 to string representation : xx.xx
          REAL32TOSTRING(size, min5, cpu.loadavg.packet[min5], 2, 2)          
          REAL32TOSTRING(size, min15, cpu.loadavg.packet[min15], 2, 2)
          --{{{ print information onto raster
          draw.text (LOADAVG.BOX.LEFT.SIDE, LOADAVG.BOX.TOP, "cpu loadavg :", FONT.6X13, LOADAVG.TEXT.COL.KEY, r.in)
          draw.text ((LOADAVG.KEY.MIN1 + 5), LOADAVG.KEY.TOP, "1 min", FONT.6X13, LOADAVG.TEXT.COL.KEY, r.in)
          draw.text ((LOADAVG.KEY.MIN5 + 5), LOADAVG.KEY.TOP, "5 min", FONT.6X13, LOADAVG.TEXT.COL.KEY, r.in)
          draw.text ((LOADAVG.KEY.MIN15 + 5), LOADAVG.KEY.TOP, "15 min", FONT.6X13, LOADAVG.TEXT.COL.KEY, r.in)
          draw.text (LOADAVG.KEY.MIN1, LOADAVG.BOX.TOP, min1, FONT.6X13, LOADAVG.TEXT.COL.VAR, r.in)        
          draw.text (LOADAVG.KEY.MIN5, LOADAVG.BOX.TOP, min5, FONT.6X13, LOADAVG.TEXT.COL.VAR, r.in)  
          draw.text (LOADAVG.KEY.MIN15, LOADAVG.BOX.TOP, min15, FONT.6X13, LOADAVG.TEXT.COL.VAR, r.in)  
          --}}}
          r.out := r.in           -- output the raster 
      :
      --}}}

      --{{{ PROC draw.cpu.clockspd
      PROC draw.cpu.clockspd (RASTER r.in, RESULT RASTER r.out, DATA.REQ.CPU.CLOCKSPD.CT! cpu.clockspd.data.ch)
        VAL INT CLOCKSPD.BOX.LEFT.SIDE IS CPU.KEY.LINE.LEFT.SIDE + 100 :
        VAL INT CLOCKSPD.CPU0.BOX.TOP IS CPU0.KEY.HEIGHT - 6 :
        VAL INT CLOCKSPD.CPU1.BOX.TOP IS CPU1.KEY.HEIGHT - 6 :
        VAL INT CLOCKSPD.VAR.COL IS COL.GREY :
        CPU.CLOCKSPD.DT clockspd.packet :
        [15]BYTE cpu0.spd, cpu1.spd :
        INITIAL INT size IS 9:
        SEQ
          cpu.clockspd.data.ch[req] ! 1
          cpu.clockspd.data.ch[data] ? clockspd.packet
        
          REAL32TOSTRING(size, cpu0.spd, clockspd.packet[cpu0], 5, 5)   
          REAL32TOSTRING(size, cpu1.spd, clockspd.packet[cpu1], 4, 3)  

--          CLAIM out!
--            SEQ
 --             print.i(size, out!)
  --            print.s("*t", out!)
   --           print.sn(cpu1.spd, out!)
          --{{{ print information onto raster
          draw.text (CLOCKSPD.BOX.LEFT.SIDE, CLOCKSPD.CPU0.BOX.TOP, cpu0.spd, FONT.6X13, CLOCKSPD.VAR.COL, r.in)
          draw.text (CLOCKSPD.BOX.LEFT.SIDE, CLOCKSPD.CPU1.BOX.TOP, cpu1.spd, FONT.6X13, CLOCKSPD.VAR.COL, r.in)
          --}}}
          r.out := r.in           -- output the raster
      :
      --}}}

      VAL INT CPU.COLOUR IS COL.RED :
      VAL INT CPU0.COLOUR IS COL.DARK.YELLOW :
      VAL INT CPU1.COLOUR IS COL.DARK.PURPLE :
      VAL INT cpu.pixel.height.scalar IS 1 :
      VAL INT cpu.pixel.width.scalar IS 4 :
      [stored.values]INT cpu.data:
      [stored.values]INT cpu0.data:
      [stored.values]INT cpu1.data:      
      CPU.UTILIZATION.DATA.DT cpu.data.packet :
      VAL INT cpu.max.value IS 100 :

    
      WHILE TRUE
        SEQ
          r.to.draw.cpu ? r                       -- receive raster
          cpu.data.ch[req] ! 1
          cpu.data.ch[data] ? cpu.data.packet           -- receive cpu data from data.store
          
          cpu.data := cpu.data.packet[cpu.data]
          cpu0.data := cpu.data.packet[cpu0.data]
          cpu1.data := cpu.data.packet[cpu1.data]

          --{{{ draw rectangle, lines, and text around the cpu data box
          draw.text (CPU.BOX.SIDE - 35, CPU.BOX.TOP - 35, "CPU status", FONT.10X20, COL.WHITE, r)
          draw.rectangle ((CPU.BOX.SIDE - 2), (CPU.BOX.TOP - 1), CPU.BOX.WIDTH, CPU.BOX.HEIGHT + 3, COL.BLUE, r)  
          draw.horizontal.line ((CPU.BOX.SIDE - 1) - 10, CPU.BOX.TOP, 10, COL.BLUE, r)    -- draw 100% line
          draw.text (CPU.BOX.SIDE - 37, CPU.BOX.TOP - 7, "100%", FONT.6X13, COL.DARK.GREY, r)
          draw.horizontal.line ((CPU.BOX.SIDE - 1) - 10, (CPU.BOX.BOTTOM - (CPU.BOX.HEIGHT / 2)), 10, COL.BLUE, r)    -- draw 50% line          
          draw.horizontal.line ((CPU.BOX.SIDE - 1), (CPU.BOX.BOTTOM - (CPU.BOX.HEIGHT / 2)), CPU.BOX.WIDTH - 1, COL.VERY.DARK.GREY, r)    -- draw 50% line through graph
          draw.text (CPU.BOX.SIDE - 31, (CPU.BOX.BOTTOM - (CPU.BOX.HEIGHT / 2)) - 7, "50%", FONT.6X13, COL.DARK.GREY, r)
          draw.horizontal.line ((CPU.BOX.SIDE - 1) - 10, CPU.BOX.BOTTOM, 10, COL.BLUE, r)    -- draw 0% line
          draw.text (CPU.BOX.SIDE - 25, CPU.BOX.BOTTOM - 7, "0%", FONT.6X13, COL.DARK.GREY, r)


          draw.horizontal.line (CPU.KEY.LINE.LEFT.SIDE, CPU.KEY.HEIGHT, CPU.KEY.LINE.LENGTH, CPU.COLOUR, r)
          draw.text (CPU.KEY.TEXT.LEFT.SIDE, CPU.KEY.HEIGHT - 6, "cpu total", FONT.6X13, COL.DARK.GREY, r)
          draw.horizontal.line (CPU.KEY.LINE.LEFT.SIDE, CPU0.KEY.HEIGHT, CPU.KEY.LINE.LENGTH, CPU0.COLOUR, r)
          draw.text (CPU.KEY.TEXT.LEFT.SIDE, CPU0.KEY.HEIGHT - 6, "cpu 0", FONT.6X13, COL.DARK.GREY, r)
          draw.horizontal.line (CPU.KEY.LINE.LEFT.SIDE, CPU1.KEY.HEIGHT, CPU.KEY.LINE.LENGTH, CPU1.COLOUR, r)
          draw.text (CPU.KEY.TEXT.LEFT.SIDE, CPU1.KEY.HEIGHT - 6, "cpu 1", FONT.6X13, COL.DARK.GREY, r)
          --}}}

          SEQ i = 0 FOR (stored.values - 1)                 -- draw cpu data onto raster
            SEQ
              connect.pixels.with.line (r, i, cpu0.data[i], i + 1, cpu0.data[i + 1], cpu.max.value, CPU.BOX.HEIGHT, CPU.BOX.WIDTH, CPU.BOX.BOTTOM, CPU.BOX.SIDE, CPU0.COLOUR)  
              connect.pixels.with.line (r, i, cpu1.data[i], i + 1, cpu1.data[i + 1], cpu.max.value, CPU.BOX.HEIGHT, CPU.BOX.WIDTH, CPU.BOX.BOTTOM, CPU.BOX.SIDE, CPU1.COLOUR)
              connect.pixels.with.line (r, i, cpu.data[i], i + 1, cpu.data[i + 1], cpu.max.value, CPU.BOX.HEIGHT, CPU.BOX.WIDTH, CPU.BOX.BOTTOM, CPU.BOX.SIDE, CPU.COLOUR)

          draw.cpu.loadavg (r, r.out, cpu.loadavg.data.ch)
          r := r.out
          draw.cpu.clockspd (r, r.out, cpu.clockspd.data.ch)
          r.from.draw.cpu ! r.out         
    :
    --}}}

    --{{{ PROC draw.memory
    PROC draw.memory (CHAN RASTER r.to.draw.memory?, r.from.draw.memory!, DATA.REQ.MEMORY.UTILIZATION.CT! mem.data.ch)
      RASTER r :
      VAL INT MEMORY.BOX.WIDTH IS 400 :
      VAL INT MEMORY.BOX.HEIGHT IS 100 :
      VAL INT MEMORY.BOX.BOTTOM IS 200 :
      VAL INT MEMORY.BOX.TOP IS (MEMORY.BOX.BOTTOM - MEMORY.BOX.HEIGHT) :
      VAL INT MEMORY.BOX.SIDE IS 100 :
      VAL INT MEMORY.MAX.VALUE IS 2100000 :
      VAL INT MEMORY.MAX.COLOUR IS COL.RED :
      VAL INT MEMORY.USED.COLOUR IS COL.GREEN :
      MEMORY.UTILIZATION.DATA.DT mem.data.packet :
      VAL INT mem.pixel.height.scalar IS 1 :
      VAL INT mem.pixel.width.scalar IS 4 :
      [stored.values]INT mem.max.data :
      [stored.values]INT mem.used.data :

      WHILE TRUE
        SEQ
          r.to.draw.memory ? r
          mem.data.ch[req] ! 1
          mem.data.ch[data] ? mem.data.packet

          mem.max.data := mem.data.packet[max]
          mem.used.data := mem.data.packet[used]
      
          --{{{ draw rectangle, lines, and text around the memory data area
          draw.text(MEMORY.BOX.SIDE - 35, MEMORY.BOX.TOP - 35, "Memory status", FONT.10X20, COL.WHITE, r)
 
          draw.rectangle (MEMORY.BOX.SIDE - 2, (MEMORY.BOX.BOTTOM - MEMORY.BOX.HEIGHT) - 1, MEMORY.BOX.WIDTH, MEMORY.BOX.HEIGHT + 3, #0000FF, r)
          draw.horizontal.line ((MEMORY.BOX.SIDE - 1) - 10, MEMORY.BOX.TOP, 10, COL.BLUE, r)    -- draw 100% line
          draw.text (MEMORY.BOX.SIDE - 37, MEMORY.BOX.TOP - 7, "100%", FONT.6X13, COL.DARK.GREY, r)
          draw.horizontal.line ((MEMORY.BOX.SIDE - 1) - 10, MEMORY.BOX.BOTTOM, 10, COL.BLUE, r)    -- draw 0% line
          draw.text (MEMORY.BOX.SIDE - 25, MEMORY.BOX.BOTTOM - 7, "0%", FONT.6X13, COL.DARK.GREY, r)
          --}}}          
 
          SEQ i = 0 FOR (stored.values - 1)
            SEQ
              connect.pixels.with.line (r, i, mem.max.data[i], i + 1, mem.max.data[i + 1], MEMORY.MAX.VALUE, MEMORY.BOX.HEIGHT, MEMORY.BOX.WIDTH, MEMORY.BOX.BOTTOM, MEMORY.BOX.SIDE, MEMORY.MAX.COLOUR)
              connect.pixels.with.line (r, i, mem.used.data[i], i + 1, mem.used.data[i + 1], MEMORY.MAX.VALUE, MEMORY.BOX.HEIGHT, MEMORY.BOX.WIDTH, MEMORY.BOX.BOTTOM, MEMORY.BOX.SIDE, MEMORY.USED.COLOUR)     

          r.from.draw.memory! r
    :
    --}}}

    --{{{ the body of 'display'
    CHAN RASTER r.to.fr, r.to.draw.cpu, r.to.draw.mem :
    PAR
      clear.display (r.in?, r.to.fr!)
      frame.rate (r.to.fr?, r.to.draw.cpu!)
      draw.cpu (r.to.draw.cpu?, r.to.draw.mem!, cpu.data.from.datastore.ch, cpu.loadavg.data.ch, cpu.clockspd.data.ch)
      draw.memory (r.to.draw.mem?, r.out!, memory.data.from.datastore.ch)
    --}}}
  :
  --}}}




  --{{{ the main body of the program
  CHAN RASTER raster.to.ds, raster.to.display :
  SYSTEM.DATA.TO.DATASTORE.CT! sys.info.to.datastore.writer :
  SYSTEM.DATA.TO.DATASTORE.CT? sys.info.to.datastore.reader :

  DATA.REQ.CPU.UTILIZATION.CT! cpu.data.from.datastore.client :
  DATA.REQ.CPU.UTILIZATION.CT? cpu.data.from.datastore.server :

  DATA.REQ.CPU.LOADAVG.CT! cpu.loadavg.data.from.datastore.client : 
  DATA.REQ.CPU.LOADAVG.CT? cpu.loadavg.data.from.datastore.server :

  DATA.REQ.CPU.CLOCKSPD.CT! cpu.clockspd.from.ds.cli :
  DATA.REQ.CPU.CLOCKSPD.CT? cpu.clockspd.from.ds.svr :

  DATA.REQ.MEMORY.UTILIZATION.CT! memory.data.from.datastore.client :
  DATA.REQ.MEMORY.UTILIZATION.CT? memory.data.from.datastore.server :

  INT cpu.percent.data :
  SEQ
    CLAIM out!
      out.string("starting up...*n", 0, out!)
    sys.info.to.datastore.writer, sys.info.to.datastore.reader := MOBILE SYSTEM.DATA.TO.DATASTORE.CT       -- instantiate channel ends for system info communication
    cpu.data.from.datastore.client, cpu.data.from.datastore.server := MOBILE DATA.REQ.CPU.UTILIZATION.CT
    cpu.loadavg.data.from.datastore.client, cpu.loadavg.data.from.datastore.server := MOBILE DATA.REQ.CPU.LOADAVG.CT
    cpu.clockspd.from.ds.cli, cpu.clockspd.from.ds.svr := MOBILE DATA.REQ.CPU.CLOCKSPD.CT
    memory.data.from.datastore.client, memory.data.from.datastore.server := MOBILE DATA.REQ.MEMORY.UTILIZATION.CT
    PAR
      read.file.system (sys.info.to.datastore.writer!)   
      data.store(sys.info.to.datastore.reader?, cpu.data.from.datastore.server?, cpu.loadavg.data.from.datastore.server?, cpu.clockspd.from.ds.svr?, memory.data.from.datastore.server?)       
      raster.display.simple ("My System Monitor", display.width, display.height, 1, raster.to.ds?, raster.to.display!)
      display(raster.to.display?, raster.to.ds!, cpu.data.from.datastore.client!, cpu.loadavg.data.from.datastore.client!, cpu.clockspd.from.ds.cli!, memory.data.from.datastore.client!)


   --   WHILE TRUE
        --SEQ
   --     system.info.communication ? sys.info
          --CLAIM out!
          --  SEQ 
          --    out.int(sys.info[cpu1.speed], 0, out!)
           --   out.string("        ", 0, out!)
          --    out.int(sys.info[cpu2.speed], 0, out!)
          --    out.string("*n", 0, out!)
   --   WHILE TRUE
   --     SEQ
   --       cpuload.ch ? cpuload
   --       REAL32TOSTRING (cpuload.string.length, cpuload.string, cpuload, 0, 0)
   --       CLAIM out!
   --         SEQ
   --           out.string(cpuload.string, 0, out!)
   --           out.string("*n", 0, out!)
  --PAR

  --}}}
: 

