#!/usr/bin/perl -w
#
# Copyright (c) 2000 Robert G. Brown <rgb@phy.duke.edu>
# Licensed according to GPL v2b (see file "Copying" in this distribution).
#
# $Id: config-host,v 1.1 2001/09/05 11:07:02 rgb Exp $
#

 use Getopt::Std;	# Single character options are plenty.
 use POSIX;		# Need the POSIX::ceil() function

#========================================================================
# Set defaults for variables
# We'll assume that 50 runs are enough for decent statistics in general
#========================================================================
 $interactive = 0;
 $verbose = 0;

#========================================================================
# Now we parse the CL with getopt standard (single character)
#========================================================================
# Option list
 getopts('hi:v:');

# Assignments
 if($opt_h) {$opt_h=0;Usage(); exit(1);}
 if($opt_i) {$interactive = $opt_i;}
 if($opt_v) {$verbose = $opt_v;}

# If leftovers, punt with error message and Usage()
 $ARGC = @ARGV;
 if($ARGC) {
   Usage("Arguments left over -- incorrect number or type of arguments");
   exit(1);
 }

 #
 # We start by "zeroing" most of the variables we hope to set to "unknown"
 # or an impossible value.  We then set what we can.  Finally we give the
 # user a chance to enter what he or she knows and fix the config file.
 # What more can we do?  The only way to keep things consistent is to
 # do as much as possible automagically and then try to help the user set
 # the rest...
 $HOSTNAME = "Unknown";
 $MOTHERBOARD = "Unknown";
 $CPUFAMILY = "Unknown";
 @CPUTYPE = ();
 @CPUVENDOR = ();
 @CPUMHZ = ();
 @CPUL1CODE = ();
 @CPUL1DATA = ();
 @CPUL2SIZE = ();
 @CPUTEST = ();
 $MEMTOTAL = "Unknown";
 $MEMCLOCK = "Unknown";
 $MEMTYPE = "Unknown";
 $MEMTEST = "16 MB";	# default small enough to fit on nearly anything.
 $MEMTESTTYPE = "SLOW";	# or "FAST", of course.
 $SYSTEM = "Unknown";
 $KERNEL = "Unknown";
 @DISK = ();
 @DISKTYPE = ();
 @DISKTEST = ();
 @NETWORK = (
  "# ethernet-100",
  "# other (sneakernet)",
);
 @NICTYPE = (
  "# Lite-On 82c168 PNIC rev 32",
  "# Nike ACG Sport Sandals, size 11",
);
 @NETHUB = (
  "# Netgear FS108 Fast Ethernet Switch",
  "# The main hall upstairs",
);
 @NETTEST = (
  "# eve",
  "# NO",
);
 @NOTES = (
"   This system has been initialized, but many variables (in particular,",
"   the network variables) are not set or are set incorrectly.  Edit the",
"   configuration file in config/$HOSTNAME to complete the installation.",
"   You should probably remove or edit this set of notes as well to show",
"   that you've done so.",
 );

 # Set variables from uname if possible.
 # Set node/hostname
 $HOSTNAME = `uname -n`;
 chop($HOSTNAME);
 $CPUFAMILY = `uname -m`;
 chop($CPUFAMILY);
 $SYSTEM = `uname -s`;
 chop($SYSTEM);
 $KERNEL = `uname -r`;
 chop($KERNEL);

 # Now we process a few things from /proc
 # /proc/cpuinfo
 open(FD, "/proc/cpuinfo") || print STDERR "$0: can't open /proc/cpuinfo";
 while (<FD>) {
   chop;
   if (/^model name/) {
      @_ = split(/:/);
      $_[1] =~ s/^\s+//;
      push(@CPUTYPE, $_[1]);
      $tmp = $CPUTYPE[0];	# Shut up the error parser
   }
   if (/^vendor_id/) {
      @_ = split(/:/);
      $_[1] =~ s/^\s+//;
      push(@CPUVENDOR, $_[1]);
      $tmp = $CPUVENDOR[0];	# Shut up the error parser
   }
   if (/^cpu MHz/) {
      @_ = split(/:/);
      $_[1] =~ s/^\s+//;
      push(@CPUMHZ, $_[1]);
      $tmp = $CPUMHZ[0];	# Shut up the error parser
   }
   if (/^cache size/) {
      @_ = split(/:/);
      $_[1] =~ s/^\s+//;
      push(@CPUL2SIZE, $_[1]);
      $tmp = $CPUL2SIZE[0];	# Shut up the error parser
   }
 }
 close(FD);
 # We also need to do something about the L1 cache size.  I personally
 # have no idea what they are for each CPU.  The best I can do is
 # mark down a guess in an hash and then print the guess.
 #
 # If you are reading this and see that the guess is wrong for your
 # linux architecture, send me the correct sizes for YOUR CPU (and the
 # associated contents of /proc/cpuinfo) and I'll try to add it.
 # alternatively I could use an lmbench binary to deduce this in real
 # time, but it probably wouldn't work exactly right and would take a
 # long time.
 $NUMCPUS = @CPUTYPE;
 for($i=0;$i<$NUMCPUS;$i++){
   $CPUTEST[$i] = NO;
   $_ = $CPUVENDOR[$i];
   if(/GenuineIntel/){
     $_ = $CPUFAMILY;
     if(/i686/){
       $CPUL1DATA[$i] = "16 KB";
       $CPUL1CODE[$i] = "16 KB";
     }
   } else {
     $CPUL1CODE[$i] = Unknown;
     $CPUL1DATA[$i] = Unknown;
   }
 }
 

 # Next, a few things from /proc/meminfo
 open(FD, "/proc/meminfo") || print STDERR "$0: can't open /proc/meminfo";
 while (<FD>) {
   chop;
   if (/^MemTotal/) {
      @_ = split(/:/);
      $_ = $_[1];
      @_ = split;
      $MEMTOTAL = POSIX::ceil($_[0]/1000);
   }
 }
 close(FD);

 # A lookup table once again sucks and will often be wrong, but
 # where can I get the information from otherwise?
 # Best bet is to let the user fix things afterwards.
 $_ = $CPUTYPE[0];
 if(/Celeron/){
   $MEMCLOCK = 67;
   $MEMTYPE = SDRAM;
 }
 if(/Pentium II\b/){
   $MEMCLOCK = 100;
   $MEMTYPE = SDRAM;
 }
 if(/Pentium III\b/){
   $MEMCLOCK = 133;
   $MEMTYPE = SDRAM;
 }

#
# There are a few things we MIGHT be able to get out of
# /var/log/dmesg, at least in recent linuces.  We'll make a stab at it,
# but a lot of this is likely to be plain wrong.  The main thing we
# ought/might be able to get out of here is the disk information.
 open(FD, "/var/log/dmesg") || print STDERR "$0: can't open /var/log/dmesg";
 while (<FD>) {
   chop;
   if (/^hd?/ && /MB/) {
      @_ = split(/:/);
      push(@DISK,$_[0]);
      $_[1] =~ s/^\s+//;
      push(@DISKTYPE,$_[1]);
      push(@DISKTEST,YES);
      $tmp = $DISK[0];	# Surpress error message
      $tmp = $DISKTYPE[0];	# Surpress error message
   }
 }
 close(FD);

#
# Now the output part.  We open the config file for output and
# overwrite it.  Then we'll offer the user a chance to edit it.
#
 open(FD, ">config/$HOSTNAME-config") || die "$0: can't open config/$HOSTNAME-config";


# First the header
#
 print FD <<EOF;
#========================================================================
#
# $HOSTNAME Config file for cpu-rate.
#
#========================================================================
#                         INSTRUCTIONS  
#========================================================================
# All variables are set according to single values as
# VARIABLENAME = VALUE
# (where value may be a text string with spaces in "'s on a single line)
# or lists of values as
# VARIABLENAME = (
#   VALUE1
#   VALUE2
#    ...
# )
# where again VALUE* can be a text string with spaces in "'s on a
# single line (where appropriate, of course).  In some cases there
# MUST be a correspondance in order and number between list elements;
# for example there MUST be a DISK and DISKTYPE entry for every disk
# registered, and there MUST be a NETWORK, NICTYPE and NETHUB entry for
# every network device.
#
# Comments can be any line that BEGINS with a "#" (like this one).
#
# If this format isn't followed, other scripts and parsers will break,
# so be careful!
#
#========================================================================
# The following variables describe the system itself and are likely
# set correctly (from uname).
#========================================================================
EOF
 # Some true facts, easily determined
 print FD "HOSTNAME = $HOSTNAME\n";
 print FD "CPUFAMILY = $CPUFAMILY\n";
 print FD "SYSTEM = $SYSTEM\n";
 print FD "KERNEL = $KERNEL\n";
 print FD <<EOF;
#========================================================================
# The motherboard will have to be entered by hand, if you know it at all.
# Since different motherboards have different clocks and chipsets,
# it certainly matters to a number of measures of system performance.
#========================================================================
EOF
 print FD "MOTHERBOARD = $MOTHERBOARD\n";
 print FD <<EOF;
#========================================================================
# This following was pulled from /proc/cpuinfo.  The values for L1
# cache size are at BEST guesses based on conditionals within the script.
# Please fix them if they are wrong, and if you are competent in perl
# PLEASE consider adding a conditional that correctly sets them for your
# system and send the diffs or fragment to rgb\@phy.duke.edu.
#
# Note that you can enable SMP tests by marking any or all CPU's YES
# in the CPUTEST vector.  This may have no effect, though, so we mark
# them all NO by default.  This will change when a decent set of SMP
# tests exists...:-)
#========================================================================
EOF
 $NUMCPUS = @CPUTYPE;
 print FD "NUMCPUS = $NUMCPUS\n";
 print FD "CPUTYPE = (\n";
 for($i=0;$i<$NUMCPUS;$i++){
   print FD "  \"$CPUTYPE[$i]\"\n";
 }
 print FD ")\n";
 print FD "CPUVENDOR = (\n";
 for($i=0;$i<$NUMCPUS;$i++){
   print FD "  $CPUVENDOR[$i]\n";
 }
 print FD ")\n";
 print FD "CPUMHZ = (\n";
 for($i=0;$i<$NUMCPUS;$i++){
   print FD "  $CPUMHZ[$i] MHz\n";
 }
 print FD ")\n";
 print FD "CPUL1DATA = (\n";
 for($i=0;$i<$NUMCPUS;$i++){
   print FD "  $CPUL1DATA[$i]\n";
 }
 print FD ")\n";
 print FD "CPUL1CODE = (\n";
 for($i=0;$i<$NUMCPUS;$i++){
   print FD "  $CPUL1CODE[$i]\n";
 }
 print FD ")\n";
 print FD "CPUL2SIZE = (\n";
 for($i=0;$i<$NUMCPUS;$i++){
   print FD "  $CPUL2SIZE[$i]\n";
 }
 print FD ")\n";
 print FD "CPUTEST = (\n";
 for($i=0;$i<$NUMCPUS;$i++){
   print FD "  $CPUTEST[$i]\n";
 }
 print FD ")\n";
 print FD <<EOF;
#========================================================================
# The total memory comes from /proc/meminfo, but the MEMCLOCK and 
# MEMTYPE guesses and are quite possibly wrong.  Once again, at least
# check them carefully and set them appropriately and if you know perl
# consider adding conditionals to the config-host script to set them
# automagically (sending diffs or fragments to rgb\@phy.duke.edu).
#
# MEMTEST specifies the amount of memory to test with memory benchmarks,
# if any are available (this is really expected to be useful to the
# lmbench suite, where I'm hoping to contribute this whole suite of
# scripts and tests).  MEMTESTTYPE can be SLOW (the default, which does
# a good job and doesn't take to long for small blocks of memory) or 
# FAST which is sloppier but might not take forever if you are testing
# a lot of memory.  MEMTEST should be comfortably larger than your L2
# cache size and comfortably smaller than your total AVAILABLE memory
# (the entry marked as column=free, row=-/+ buffers/cache by the "free"
# command on a linux box).
#========================================================================
EOF
 print FD "MEMTOTAL = $MEMTOTAL MB\n";
 print FD "MEMCLOCK = $MEMCLOCK MHz\n";
 print FD "MEMTYPE = $MEMTYPE\n";
 print FD "MEMTEST = $MEMTEST\n";
 print FD "MEMTESTTYPE = $MEMTESTTYPE\n";

 print FD <<EOF;
#========================================================================
# The config script parses /var/log/dmesg to extract disk information for
# ide disks.  This works pretty well for the ide disks I have to test on
# a system that saves the boot message in /var/log/dmesg.  An utter lack
# of standards for driver startup messages from scsi controllers makes this
# very difficult to arrange automagically for SCSI devices.  I'd love to
# have contributions from anybody who has a clever idea of how to arrange
# this, but most of the solutions I can contrive require something 
# to be run (e.g. fdisk -l /dev/sd?) that requires root privileges.  Once
# again, it would really be very nice if a "standard form" list of devices
# was maintained by the kernel that indicated (where possible) the device
# name, manufacturer, part number, size, speed, driver, and so forth.  This
# needn't be a formal "registry" in that the system uses it at all, but it
# would tremendously increase the information available to users, admins,
# engineers and programmers outside of rootspace.  Oh, well.
# 
# As before, one can enable disk-based benchmarks to be run on any
# disks in the DISK list via the associated DISKTEST list.  We currently
# assume that all disks should be tested, if possible.
#========================================================================
EOF
 $NUMDISKS = @DISK;
 print FD "DISK = (\n";
 for($i=0;$i<$NUMDISKS;$i++){
   print FD "  /dev/$DISK[$i]\n";
 }
 print FD ")\n";
 print FD "DISKTYPE = (\n";
 for($i=0;$i<$NUMDISKS;$i++){
   print FD "  \"$DISKTYPE[$i]\"\n";
 }
 print FD ")\n";
 print FD "DISKTEST = (\n";
 for($i=0;$i<$NUMDISKS;$i++){
   print FD "  $DISKTEST[$i]\n";
 }
 print FD ")\n";

 print FD <<EOF;
#========================================================================
# Networks just plain cannot be automagically identified in any sane way.
# The one place that they are almost always identified is in 
# /var/log/messages, but logfiles are typically rotated and may even be
# stored on a completely different system so the required information may
# have long since gone away.  Network drivers inserted as modules do not
# typically log their startup messages into /var/log/dmesg.  Even those
# startup messages don't typically tell a user useful little tidbits like
# whether the connection is (e.g.) 10 or 100 Base full or half duplex, and
# finally even if one DOES have the network drivers' startup messages
# handy, they aren't in anything like a standard form that can be uniformly
# parsed.  Sigh.
#
# I can see no alternative, then, to entering all of this by hand, and I
# cannot even tell you how to go about determining the values you should
# enter short of suggesting that you look in /var/log/dmesg or
# /var/log/messages (quite possibly after restarting your network from
# the point of module insertion up or even rebooting).  The entries below
# are therefore ALL DUMMIES (note the comment "#" signs) and must be
# edited to be functional.
#
# Note also that one MUST select the NETWORK(s) itself from the following
# list:
#  ethernet                aka 10baseT, thinnet, thicknet, etc
#  ethernet-100            aka 100baseT, 100VG
#  ethernet-1000           gigabit ethernet
#  myrinet
#  fddi                    aka cddi
#  atm
#  hippi
#  other                   (describe in NICTYPE and/or NETHUB)
# to facilitate parsing by things that might need to know later.  Feel
# free to send me suggestions for new entries (preferrably with descriptions)
# at rgb\@phy.duke.edu.
#
# To enable a test of a network device, enter the name of a target host
# in the NETTEST vector in the appropriate slot.  rsh or ssh (and rcp or
# scp) must work between the current host and the target host for the
# user conducting the benchmark, usually/ideally root (to enable the
# disk tests in lmbench).
#========================================================================
EOF
 $NUMNETS = @NETWORK;
 print FD "NETWORK = (\n";
 for($i=0;$i<$NUMNETS;$i++){
   print FD "  $NETWORK[$i]\n";
 }
 print FD ")\n";
 print FD "NICTYPE = (\n";
 for($i=0;$i<$NUMNETS;$i++){
   print FD "  \"$NICTYPE[$i]\"\n";
 }
 print FD ")\n";
 print FD "NETHUB = (\n";
 for($i=0;$i<$NUMNETS;$i++){
   print FD "  \"$NETHUB[$i]\"\n";
 }
 print FD ")\n";
 print FD "NETTEST = (\n";
 for($i=0;$i<$NUMNETS;$i++){
   print FD "  $NETTEST[$i]\n";
 }
 print FD ")\n";

 print FD <<EOF;
#========================================================================
# NOTES is a catchall where you can enter whatever you like to describe
# your system that isn't covered above.  This can be something as simple
# as "This system belongs to Robert G. Brown <rgb\@phy.duke.edu>", or as
# complicated as a detailed description of a beowulf for which this system
# is a node.  It can be nothing at all.  As you can see, quotes are not
# necessary although one may have to use care with symbols like \@ or
# \$ as they are used by perl.  
# 
# Be careful (as always) to preserve the general format of single lines
# sandwiched between the ()'s or later scripts will be unable to parse 
# it correctly.
#========================================================================
EOF
 print FD "NOTES = (\n";
 foreach $line (@NOTES){
   print FD "$line\n";
 }
 print FD ")\n";
 print FD <<EOF;
#========================================================================
EOF
 close(FD);

 print "OK, host configuration is now in config/$HOSTNAME-config\n";
 print "HOWEVER, it is almost certainly WRONG.  Please edit this\n";
 print "file with your favorite editor and add missing entries\n";
 print "or correct incorrect ones.  Note well that L1 cache size\n";
 print "and main memory clock and type are at best guesses.  The\n";
 print "disk(s) might be correct if they are ide.  The network is\n";
 print "not set at all (and should be).\n"; 

 

 exit(0);


sub Usage {

 my $message = shift;
 if($message) {print STDERR "Error: $message\n";}
 print STDERR "Usage:\n";
 print STDERR "  config-host (no arguments)\n";
 exit;
}

