/* Linux meminfo, using XView toolkit and /proc
 * Copyright (C) 1993  Kenneth Osterberg
 * Copyright (C) 1995  Jakub Jelinek
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <xview/xview.h>
#include <xview/panel.h>
#include <xview/frame.h>
#include <xview/notify.h>

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <math.h>

unsigned short icon_bits [] = {
#include <images/perfmeter.icon>
};

#define VERSION "v1.1"

struct meminfo {
  int meminfofd;
  int total;
  int used;
  int free;
  int shared;
  int buffers;
  int swaptotal;
  int swapused;
  int swapfree;
  int totmegs;
  int swapmegs;
  int bytes_per_tick;
  int swapbytes_per_tick;
};

struct loadinfo {
  int loadavgfd;
  int uptimefd;
  double load1;
  double load5;
  double load15;
  int uptime;
  int updays;
  int uphours;
  int upminutes;
  int load_per_tick;
};

#define WINWID 560
#define WINHGT 224

#define G_Y 36
#define G_H 124
#define G_USED_X 8
#define G_SHARED_X G_USED_X + 66
#define G_BUFFERS_X G_SHARED_X + 78
#define G_SWAP_X G_BUFFERS_X + 104
#define G_LOAD1_X G_SWAP_X + 92
#define G_LOAD5_X G_LOAD1_X + 66
#define G_LOAD15_X G_LOAD5_X + 66
#define T_VALUE_Y G_Y + G_H + 24

struct itimerval timer;
#define PERIODSECS 30
int periodic = PERIODSECS + 1;
int freq = 2;
 
/**************************************************/
Frame w;
Panel_gauge_item g_used, g_shared, g_buffers, g_swap, g_load1, g_load5, 
    g_load15;
Panel_message_item t_usedvalue, t_sharedvalue, t_buffersvalue, t_swapvalue,
    t_load1value, t_load5value, t_load15value, t_meminfo, t_swapinfo, 
    t_loadinfo, t_uptime;
Panel_numeric_text_item n_time;
/**************************************************/
void freqHandler(Panel_item, Event *);
Notify_value update(void);
void updateTextValues(struct meminfo *);
void roundMeg(int, int *, int *);
void periodicUpdate(void);
struct loadinfo *readLoadinfo(void);
struct meminfo *readMeminfo(void);
/**************************************************/
main (int argc, char **argv) {
  char iconfile[128];
  struct meminfo *mi;
  char memlabel[64], swaplabel[64];
  Panel c;

  sprintf(iconfile, "%s/include/images/perfmeter.icon",
          getenv("OPENWINHOME"));

  if (chdir("/proc")) {
    perror("chdir() to /proc failed");
    exit(1);
  }
  
  xv_init (XV_INIT_ARGC_PTR_ARGV, &argc, argv, NULL);

  w = xv_create (XV_NULL, FRAME,
      XV_WIDTH, WINWID,
      XV_HEIGHT, WINHGT,
      FRAME_SHOW_FOOTER, FALSE,
      FRAME_SHOW_RESIZE_CORNER, FALSE,
      FRAME_LABEL, "Linux Meminfo  " VERSION,
      FRAME_ICON, xv_create (XV_NULL, ICON,
          ICON_WIDTH, 32,
          ICON_HEIGHT, 32,
          ICON_IMAGE, xv_create (XV_NULL, SERVER_IMAGE,
              XV_WIDTH, 32,
              XV_HEIGHT, 32,
              SERVER_IMAGE_BITS, icon_bits,
              NULL),
          NULL),
      XV_SHOW, TRUE,
      NULL);

  c = xv_create (w, PANEL,
        XV_HELP_DATA, "meminfo:Canvas",
	NULL);      

  n_time = xv_create (c, PANEL_NUMERIC_TEXT,
        XV_X, 8,
        XV_Y, WINHGT - 20,
        PANEL_VALUE, 2,
        PANEL_MIN_VALUE, 0,
        PANEL_MAX_VALUE, 60,
        PANEL_VALUE_DISPLAY_LENGTH, 2,
        PANEL_NOTIFY_PROC, freqHandler,
        XV_HELP_DATA, "meminfo:UpdateInput",
        PANEL_LABEL_STRING, "Update freq.",
        NULL);

  t_uptime = xv_create (c, PANEL_MESSAGE,
        XV_X, G_BUFFERS_X + 48,
        XV_Y, WINHGT - 20,
        PANEL_LABEL_STRING, "Uptime: ",
        XV_HELP_DATA, "meminfo:UptimeText",
        NULL);

  mi = readMeminfo();

  g_used = xv_create (c, PANEL_GAUGE,
        PANEL_SHOW_RANGE, FALSE,
        XV_X, G_USED_X,
        XV_Y, G_Y,
        PANEL_DIRECTION, PANEL_VERTICAL,
        PANEL_GAUGE_WIDTH, G_H,
        PANEL_MIN_VALUE, 0,
        PANEL_MAX_VALUE, 256,
        XV_HELP_DATA, "meminfo:UsedGauge",
        PANEL_TICKS, mi->totmegs + 1,
        PANEL_LABEL_STRING, "Used",
        NULL);

  g_shared = xv_create (c, PANEL_GAUGE,
        PANEL_SHOW_RANGE, FALSE,
        XV_X, G_SHARED_X,
        XV_Y, G_Y,
        PANEL_DIRECTION, PANEL_VERTICAL,
        PANEL_GAUGE_WIDTH, G_H,
        PANEL_MIN_VALUE, 0,
        PANEL_MAX_VALUE, 256,
        XV_HELP_DATA, "meminfo:SharedGauge",
        PANEL_TICKS, mi->totmegs + 1,
        PANEL_LABEL_STRING, "Shared",
        NULL);

  g_buffers = xv_create (c, PANEL_GAUGE,
        PANEL_SHOW_RANGE, FALSE,
        XV_X, G_BUFFERS_X,
        XV_Y, G_Y,
        PANEL_DIRECTION, PANEL_VERTICAL,
        PANEL_GAUGE_WIDTH, G_H,
        PANEL_MIN_VALUE, 0,
        PANEL_MAX_VALUE, 256,
        XV_HELP_DATA, "meminfo:BuffersGauge",
        PANEL_TICKS, mi->totmegs + 1,
        PANEL_LABEL_STRING, "Buffers",
        NULL);

  g_swap = xv_create (c, PANEL_GAUGE,
        PANEL_SHOW_RANGE, FALSE,
        XV_X, G_SWAP_X,
        XV_Y, G_Y,
        PANEL_DIRECTION, PANEL_VERTICAL,
        PANEL_GAUGE_WIDTH, G_H,
        PANEL_MIN_VALUE, 0,
        PANEL_MAX_VALUE, 256,
        XV_HELP_DATA, "meminfo:SwapGauge",
        PANEL_TICKS, mi->swapmegs + 1,
        PANEL_LABEL_STRING, "Swap",
        NULL);

  g_load1 = xv_create (c, PANEL_GAUGE,
        PANEL_SHOW_RANGE, FALSE,
        XV_X, G_LOAD1_X,
        XV_Y, G_Y,
        PANEL_DIRECTION, PANEL_VERTICAL,
        PANEL_GAUGE_WIDTH, G_H,
        PANEL_MIN_VALUE, 0,
        PANEL_MAX_VALUE, 256,
        XV_HELP_DATA, "meminfo:Load1Gauge",
        PANEL_TICKS, 5,
        PANEL_LABEL_STRING, "1 Min",
        NULL);

  g_load5 = xv_create (c, PANEL_GAUGE,
        PANEL_SHOW_RANGE, FALSE,
        XV_X, G_LOAD5_X,
        XV_Y, G_Y,
        PANEL_DIRECTION, PANEL_VERTICAL,
        PANEL_GAUGE_WIDTH, G_H,
        PANEL_MIN_VALUE, 0,
        PANEL_MAX_VALUE, 256,
        XV_HELP_DATA, "meminfo:Load5Gauge",
        PANEL_TICKS, 5,
        PANEL_LABEL_STRING, "5 Min",
        NULL);

  g_load15 = xv_create (c, PANEL_GAUGE,
        PANEL_SHOW_RANGE, FALSE,
        XV_X, G_LOAD15_X,
        XV_Y, G_Y,
        PANEL_DIRECTION, PANEL_VERTICAL,
        PANEL_GAUGE_WIDTH, G_H,
        PANEL_MIN_VALUE, 0,
        PANEL_MAX_VALUE, 256,
        XV_HELP_DATA, "meminfo:Load15Gauge",
        PANEL_LABEL_STRING, "15 Min",
        PANEL_TICKS, 5,
        NULL);

  t_usedvalue = xv_create (c, PANEL_MESSAGE,
        XV_X, G_USED_X + 24, XV_Y, T_VALUE_Y,
        XV_HELP_DATA, "meminfo:UsedValue",
        NULL);
        
  t_sharedvalue = xv_create (c, PANEL_MESSAGE,
        XV_X, G_SHARED_X + 32, XV_Y, T_VALUE_Y,
        XV_HELP_DATA, "meminfo:SharedValue",
        NULL);
        
  t_buffersvalue = xv_create (c, PANEL_MESSAGE,
        XV_X, G_BUFFERS_X + 32, XV_Y, T_VALUE_Y,
        XV_HELP_DATA, "meminfo:BuffersValue",
        NULL);
        
  t_swapvalue = xv_create (c, PANEL_MESSAGE,
        XV_X, G_SWAP_X + 24, XV_Y, T_VALUE_Y,
        XV_HELP_DATA, "meminfo:SwapValue",
        NULL);
        
  t_load1value = xv_create (c, PANEL_MESSAGE,
        XV_X, G_LOAD1_X + 32, XV_Y, T_VALUE_Y,
        XV_HELP_DATA, "meminfo:Load1Value",
        NULL);
        
  t_load5value = xv_create (c, PANEL_MESSAGE,
        XV_X, G_LOAD5_X + 32, XV_Y, T_VALUE_Y,
        XV_HELP_DATA, "meminfo:Load5Value",
        NULL);
        
  t_load15value = xv_create (c, PANEL_MESSAGE,
        XV_X, G_LOAD15_X + 40, XV_Y, T_VALUE_Y,
        XV_HELP_DATA, "meminfo:Load15Value",
        NULL);
        
  t_meminfo = xv_create (c, PANEL_MESSAGE,
        XV_X, 8, XV_Y, (G_Y >> 1) - 4,
        XV_HELP_DATA, "meminfo:MemInfo",
        NULL);
        
  t_swapinfo = xv_create (c, PANEL_MESSAGE,
        XV_X, G_BUFFERS_X + 56, XV_Y, (G_Y >> 1) - 4,
        XV_HELP_DATA, "meminfo:SwapInfo",
        NULL);
        
  t_loadinfo = xv_create (c, PANEL_MESSAGE,
        XV_X, G_LOAD1_X + 64, XV_Y, (G_Y >> 1) - 4,
        XV_HELP_DATA, "meminfo:LoadInfo",
        NULL);
        
  update();

  sprintf(memlabel, "Memory   (total %d)", mi->total);
  sprintf(swaplabel, "Swap (total %d)", mi->swaptotal);
  xv_set (t_meminfo,
      PANEL_LABEL_STRING, memlabel,
      NULL);
  xv_set (t_swapinfo,
      PANEL_LABEL_STRING, swaplabel,
      NULL);
  xv_set (t_loadinfo,
      PANEL_LABEL_STRING, "Load Averages",
      NULL);

  timer.it_value.tv_sec = 1;
  timer.it_value.tv_usec = 0;
  timer.it_interval.tv_sec = 1;
  timer.it_interval.tv_usec = 0;

  notify_set_itimer_func(w, update, ITIMER_REAL, &timer, NULL);

  xv_main_loop (w);
  exit(0);
}
/**************************************************/
void freqHandler(Panel_item item, Event *event) {
/* Called when user changes NumericInput */

  freq = (int) xv_get (item, PANEL_VALUE);
  if (freq > 0) {
    timer.it_value.tv_sec = freq;
    timer.it_value.tv_usec = 0;
    timer.it_interval.tv_sec = freq;
    timer.it_interval.tv_usec = 0;
  }
  else {
    timer.it_value.tv_sec = 0;
    timer.it_value.tv_usec = 500000L;
    timer.it_interval.tv_sec = 0;
    timer.it_interval.tv_usec = 500000L;
  }
  notify_set_itimer_func(w, update, ITIMER_REAL, &timer, NULL);
} 
/**************************************************/
Notify_value update(void) {
/* Called to refresh the data */

  struct meminfo *m = readMeminfo();
  updateTextValues(m);
  xv_set (g_used,
      PANEL_VALUE, m->used / m->bytes_per_tick,
      NULL);
  xv_set (g_shared,
      PANEL_VALUE, m->shared / m->bytes_per_tick,
      NULL);
  xv_set (g_buffers,
      PANEL_VALUE, m->buffers / m->bytes_per_tick,
      NULL);
  if (m->swapbytes_per_tick != 0)
    xv_set (g_swap,
      PANEL_VALUE, m->swapused / m->swapbytes_per_tick,
      NULL);
  else
    xv_set (g_swap,
      PANEL_VALUE, 0,
      NULL);

  periodic += (freq) ? freq : 1;
  if (periodic > PERIODSECS) {
    periodicUpdate();
    periodic = 0;
  }
  return (Notify_value)NOTIFY_DONE;
}
/**************************************************/
void updateTextValues(struct meminfo *m) {
/* Update the TextItems below the gauges which show
 * the numeric values depicted by the gauges */
  char str[16];
  int n, d;

  roundMeg(m->used, &n, &d);
  sprintf(str, "%d.%d MB", n, d);
  xv_set (t_usedvalue,
      PANEL_LABEL_STRING, str,
      NULL);
  roundMeg(m->shared, &n, &d);
  sprintf(str, "%d.%d MB", n, d);
  xv_set (t_sharedvalue,
      PANEL_LABEL_STRING, str,
      NULL);
  roundMeg(m->buffers, &n, &d);
  sprintf(str, "%d.%d MB", n, d);
  xv_set (t_buffersvalue,
      PANEL_LABEL_STRING, str,
      NULL);
  roundMeg(m->swapused, &n, &d);
  sprintf(str, "%d.%d MB", n, d);
  xv_set (t_swapvalue,
      PANEL_LABEL_STRING, str,
      NULL);
}
/**************************************************/
void roundMeg(int val, int *pn, int *pd) {
/* Turn value to megabyte integer and decimal
 * parts. (Incorrectly) */
  int n = *pn;
  int d = *pd;
  n = val >> 20;
  d = (val & 0xf0000) >> 16;
  switch (d) {
  case 0: case 1:  d = 0; break;
  case 2: case 3:  d = 1; break;
  case 4:          d = 2; break;
  case 5: case 6:  d = 3; break;
  case 7:          d = 4; break;
  case 8: case 9:  d = 5; break;
  case 10: case 11:d = 6; break;
  case 12:         d = 7; break;
  case 13: case 14:d = 8; break;
  case 15:         d = 9; break;
  default:
    fprintf(stderr, "roundMeg() Shouldn't happen\n");
    exit(1);
  }
  *pn = n; *pd = d;
}
/**************************************************/
void periodicUpdate(void) {
/* Called to update the uptime and loadaverages
 * It is called more seldom than the memory values,
 * because the uptime and loadaverages do not change
 * as often. */
  char upstr[64];
  char loadstr[12];
  struct loadinfo *li;

  li = readLoadinfo(); sprintf(upstr, "Uptime: %d %s %d %s %d %s",
          li->updays, (li->updays == 1) ? "Day" : "Days",
          li->uphours, (li->uphours == 1) ? "Hour" : "Hours",
          li->upminutes, (li->upminutes == 1) ? "Minute" : "Minutes");
  xv_set (t_uptime,
      PANEL_LABEL_STRING, upstr,
      NULL);

  xv_set (g_load1,
      PANEL_VALUE, (int)(li->load_per_tick * li->load1),
      NULL);
  xv_set (g_load5,
      PANEL_VALUE, (int)(li->load_per_tick * li->load5),
      NULL);
  xv_set (g_load15,
      PANEL_VALUE, (int)(li->load_per_tick * li->load15),
      NULL);

  sprintf(loadstr, "%1.2f", li->load1);
  xv_set (t_load1value,
      PANEL_LABEL_STRING, loadstr,
      NULL);
  sprintf(loadstr, "%1.2f", li->load5);
  xv_set (t_load5value,
      PANEL_LABEL_STRING, loadstr,
      NULL);
  sprintf(loadstr, "%1.2f", li->load15);
  xv_set (t_load15value,
      PANEL_LABEL_STRING, loadstr,
      NULL);
}
/**************************************************/
struct loadinfo *readLoadinfo(void) {
/* Read /proc/loadavg and /proc/uptime, and return
 * the new values. */
  static struct loadinfo load = { 0, 0 };
  char buf[4097];
  char *tok;
  int cnt;

  if (load.loadavgfd <= 0)
    load.loadavgfd = open("loadavg", O_RDONLY);
  else
    lseek(load.loadavgfd, 0, SEEK_SET);
  if (load.loadavgfd <= 0) {
    fprintf(stderr, "Cannot open /proc/loadavg\n");
    exit(1);
  }
  cnt = read(load.loadavgfd, buf, 4096);
  if (cnt <= 0) {
    fprintf(stderr, "Read failure on /proc/loadavg\n");
    exit(1);
  }
  buf[cnt] = '\0';
  tok = strtok(buf, " \t\r\n");
  if (tok == NULL) {
    fprintf(stderr, "Meminfo: Unknown format on /proc/loadavg\n");
    exit(1);
  }
  load.load1 = atof(tok);
  tok = strtok(NULL, " \t\r\n");
  if (tok == NULL) {
    fprintf(stderr, "Meminfo: Unknown format on /proc/loadavg\n");
    exit(1);
  }
  load.load5 = atof(tok);
  tok = strtok(NULL, " \t\r\n");
  if (tok == NULL) {
    fprintf(stderr, "Meminfo: Unknown format on /proc/loadavg\n");
    exit(1);
  }
  load.load15 = atof(tok);

  if (load.uptimefd <= 0)
    load.uptimefd = open("uptime", O_RDONLY);
  else
    lseek(load.uptimefd, 0, SEEK_SET);
  if (load.uptimefd <= 0) {
    fprintf(stderr, "Cannot open /proc/uptime\n");
    exit(1);
  }
  cnt = read(load.uptimefd, buf, 4096);
  if (cnt <= 0) {
    fprintf(stderr, "Read failure on /proc/uptime\n");
    exit(1);
  }
  buf[cnt] = '\0';
  tok = strtok(buf, " \t\r\n");
  if (tok == NULL) {
    fprintf(stderr, "Meminfo: Unknown format on /proc/uptime\n");
    exit(1);
  }
  load.uptime = atoi(tok);
  load.updays = load.uptime / 86400;
  load.uphours = (load.uptime % 86400) / 3600;
  load.upminutes = (load.uptime % 3600) / 60;
  load.load_per_tick = 64;
  return &load;
}
/**************************************************/
struct meminfo *readMeminfo(void) {
/* Read /proc/meminfo and return the new values */
  static struct meminfo mem = { 0 };
  char buf[4097];
  char *tok, *nextline;
  int cnt;

  if (mem.meminfofd <= 0)
    mem.meminfofd = open("meminfo", O_RDONLY);
  else
    lseek(mem.meminfofd, 0, SEEK_SET);
  if (mem.meminfofd <= 0) {
    fprintf(stderr, "Cannot open /proc/meminfo\n");
    exit(1);
  }
  cnt = read(mem.meminfofd, buf, 4096);
  if (cnt <= 0) {
    perror("Read failure on /proc/meminfo");
    exit(1);
  }
  buf[cnt] = '\0';

  if ((tok = strtok(buf, "\r\n")) == NULL) {
BadFormat:
    fprintf(stderr, "Unknown format on /proc/meminfo\n");
    exit(1);
  }
  
  if ((tok = strtok(NULL, "\r\n")) == NULL)
    goto BadFormat;
  nextline = tok + strlen(tok) + 1;

  tok = strtok(tok, " \t\r\n");
  if ((tok == NULL) || strcmp(tok, "Mem:"))
    goto BadFormat;

  if ((tok = strtok(NULL, " \t\r\n")) != NULL)
    mem.total = atoi(tok);
  else
    goto BadFormat;
  if ((tok = strtok(NULL, " \t\r\n")) != NULL)
    mem.used = atoi(tok);
  else
    goto BadFormat;
  if ((tok = strtok(NULL, " \t\r\n")) != NULL)
    mem.free = atoi(tok);
  else
    goto BadFormat;
  if ((tok = strtok(NULL, " \t\r\n")) != NULL)
    mem.shared = atoi(tok);
  else
    goto BadFormat;
  if ((tok = strtok(NULL, " \t\r\n")) != NULL)
    mem.buffers = atoi(tok);
  else
    goto BadFormat;

  tok = strtok(nextline, " \t\r\n");
  if ((tok == NULL) || strcmp(tok, "Swap:"))
    goto BadFormat;
  if ((tok = strtok(NULL, " \t\r\n")) != NULL)
    mem.swaptotal = atoi(tok);
  else
    goto BadFormat;
  if ((tok = strtok(NULL, " \t\r\n")) != NULL)
    mem.swapused = atoi(tok);
  else
    goto BadFormat;
  if ((tok = strtok(NULL, " \t\r\n")) != NULL)
    mem.swapfree = atoi(tok);
  else
    goto BadFormat;

  mem.totmegs = (mem.total / (1024 * 1024)) + 1;
  mem.swapmegs = (mem.swaptotal / (1024 * 1024)) + 1;
  mem.bytes_per_tick = mem.total >> 8; 
  mem.swapbytes_per_tick = mem.swaptotal >> 8; 

  return(&mem);
}
/**************************************************/
