/* Copyright (C) 1988, 1989 Free Software Foundation, Inc.
This file is part of GNU Make.

GNU Make 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 1, or (at your option)
any later version.

GNU Make 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.

You should have received a copy of the GNU General Public License
along with GNU Make; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include "make.h"
#include "commands.h"
#include "job.h"

#ifdef uMAX

#define LDAV_BASED

/*
 *  uMAX 4.2, which runs on the Encore Multimax multiprocessor, does not
 *  have a /dev/kmem.  Information about the workings of the running kernel
 *  can be gathered with inq_stats system calls.
 */
#include <stdio.h>
#include <signal.h>

#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/sysdefs.h>
#include <sys/syscall.h>
#include <sys/statistics.h>
#include <sys/sysstats.h>
#include <sys/cpudefs.h>
#include <sys/cpustats.h>
#include <sys/procstats.h>


static double
load_average ()
{
  static unsigned int cpus = 0, samples;
  struct proc_summary proc_sum_data;
  struct stat_descr proc_info;
  double load;
  register unsigned int i, j;

  if (cpus == 0)
    {
      register unsigned int c, i;
      struct cpu_config conf;
      struct stat_descr desc;

      desc.sd_next = 0;
      desc.sd_subsys = SUBSYS_CPU;
      desc.sd_type = CPUTYPE_CONFIG;
      desc.sd_addr = (char *) &conf;
      desc.sd_size = sizeof conf;

      if (inq_stats (1, &desc))
	return 0.0;

      c = 0;
      for (i = 0; i < conf.config_maxclass; ++i)
	{
	  struct class_stats stats;
	  bzero ((char *) &stats, sizeof stats);

	  desc.sd_type = CPUTYPE_CLASS;
	  desc.sd_objid = i;
	  desc.sd_addr = (char *) &stats;
	  desc.sd_size = sizeof stats;

	  if (inq_stats (1, &desc))
	    return 0.0;

	  c += stats.class_numcpus;
	}
      cpus = c;
      samples = cpus < 2 ? 3 : (2 * cpus / 3);
    }
  
  proc_info.sd_next = 0;
  proc_info.sd_subsys = SUBSYS_PROC;
  proc_info.sd_type = PROCTYPE_SUMMARY;
  proc_info.sd_addr = (char *) &proc_sum_data;
  proc_info.sd_size = sizeof (struct proc_summary);
  proc_info.sd_sizeused = 0;
  
  if (inq_stats (1, &proc_info) != 0)
    return 0.0;
  
  load = proc_sum_data.ps_nrunnable;
  j = 0;
  for (i = samples - 1; i > 0; --i)
    {
      load += proc_sum_data.ps_nrun[j];
      if (j++ == PS_NRUNSIZE)
	j = 0;
    }
  
  return (load / samples / cpus);
}

#else	/* Not uMAX.  */

#ifndef NO_LDAV

#define LDAV_BASED

#ifndef	KERNEL_FILE_NAME
#define KERNEL_FILE_NAME "/vmunix"
#endif
#ifndef	LDAV_SYMBOL
#define LDAV_SYMBOL "_avenrun"
#endif
#ifndef LDAV_TYPE
#define LDAV_TYPE long int
#endif
#ifndef LDAV_CVT
#define LDAV_CVT ((double) load)
#endif

#include <nlist.h>
#ifdef	NLIST_NAME_UNION
#define	nl_name	n_un.n_name
#else
#define	nl_name	n_name
#endif

#ifdef	USG
#include <fcntl.h>
#else
#include <sys/file.h>
#endif

/* Return the current load average as a double.  */

static double
load_average ()
{
  extern int nlist ();
  LDAV_TYPE load;
  static int complained = 0;
  static int kmem = -1;
  static unsigned long int offset = 0;

  if (kmem < 0)
    {
      kmem = open ("/dev/kmem", O_RDONLY);
      if (kmem < 0)
	{
	  if (!complained)
	    perror_with_name ("open: ", "/dev/kmem");
	  goto lose;
	}
    }

  if (offset == 0)
    {
      struct nlist nl[2];

#ifdef	NLIST_NAME_ARRAY
      strcpy (nl[0].nl_name, LDAV_SYMBOL);
      strcpy (nl[1].nl_name, "");
#else	/* Not NLIST_NAME_ARRAY.  */
      nl[0].nl_name = LDAV_SYMBOL;
      nl[1].nl_name = 0;
#endif	/* NLIST_NAME_ARRAY.  */

      if (nlist (KERNEL_FILE_NAME, nl) < 0 || nl[0].n_type == 0)
	{
	  if (!complained)
	    perror_with_name ("nlist: ", KERNEL_FILE_NAME);
	  goto lose;
	}
      offset = nl[0].n_value;
    }

  if (lseek (kmem, offset, 0) == -1L)
    {
      if (!complained)
	perror_with_name ("lseek: ", "/dev/kmem");
      goto lose;
    }
  if (read (kmem, &load, sizeof load) < 0)
    {
      if (!complained)
	perror_with_name ("read: ", "/dev/kmem");
      goto lose;
    }

  if (complained)
    {
      error ("Load average limits will be enforced again.");
      complained = 0;
    }
  return LDAV_CVT;

 lose:;
  if (!complained)
    {
      error ("Load average limits will not be enforced.");
      complained = 1;
    }
  return 0.0;
}

#endif	/* Not NO_LDAV.  */

#endif	/* uMAX.  */


#ifdef LDAV_BASED

extern unsigned int job_slots_used;

extern int sleep ();

/* Don't return until a job should be started.  */

void
wait_to_start_job ()
{
  register unsigned int loops = 0;

  if (max_load_average < 0.0)
    return;

  while (job_slots_used > 1)
    {
      double load = load_average ();

      if (load < max_load_average)
	return;

      ++loops;
      if (loops == 5 || load > max_load_average * 2)
	{
	  /* If the load is still too high after five loops or it is very
	     high, just wait for a child to die before checking again.  */
	  loops = 0;
	  wait_for_children (1, 0);
	}
      else
	/* Don't check the load again immediately, because that will
	   just worsen the load.  Check it progressively more slowly.  */
	sleep (loops);
    }
}

#else	/* Not LDAV_BASED.  */

/* How else to do it?  */

void
wait_to_start_job ()
{
  return;
}
#endif	/* LDAV_BASED.  */
