/*
 * fastreadwrite.c
 *
 * Test drive read/write speed.
 *
 * Author: Nat Lanza
 */
/*
 * Copyright (c) of Carnegie Mellon University, 1999.
 *
 * Permission to reproduce, use, and prepare derivative works of
 * this software for internal use is granted provided the copyright
 * and "No Warranty" statements are included with all reproductions
 * and derivative works. This software may also be redistributed
 * without charge provided that the copyright and "No Warranty"
 * statements are included in all redistributions.
 *
 * NO WARRANTY. THIS SOFTWARE IS FURNISHED ON AN "AS IS" BASIS.
 * CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER
 * EXPRESSED OR IMPLIED AS TO THE MATTER INCLUDING, BUT NOT LIMITED
 * TO: WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY
 * OF RESULTS OR RESULTS OBTAINED FROM USE OF THIS SOFTWARE. CARNEGIE
 * MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT
 * TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
 */

#include <nasd/nasd_options.h>
#include <nasd/nasd_site.h>
#include <nasd/nasd_pdrive.h>
#include <nasd/nasd_pdrive_client.h>
#include <nasd/nasd_pdrive_client_kpdev.h>
#include <nasd/nasd_pdev.h>
#include <nasd/nasd_timer.h>
#include <nasd/nasd_mem.h>
#include <nasd/nasd_getopt.h>
#include <nasd/nasd_itypes.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>

#include <malloc.h>

char *progname;

nasd_error_string_t error_text;
#define DEF_BLOCKSIZE 131072
int blocksize = DEF_BLOCKSIZE;
#define DEF_NITERS 10
int niters = DEF_NITERS;
nasd_identifier_t nasdid = NASD_ID_NULL;
int partnum = 1;
int suppress_noop = 0;

int do_ufs;

int skip_write = 0;
int skip_read = 0;
int nondefault_binding = 0;

char *server_name;
nasd_drive_handle_t h;
int binding_type;
int binding_args_len;
void *binding_args;
nasd_drive_param_kpdev_t kpbind;

int eject_obj = 0;
int flush_obj = 0;
int do_compare = 0;
int do_range = 0;

nasd_cookie_t cookie;
nasd_sec_keyring_t keys;
nasd_security_param_t sec_param;
nasd_uint16 protection;

char *buffer = NULL;

#define CHUNK_SIZE 4096
nasd_mem_list_t *memlist = NULL;
int list_len;

typedef struct iter_info_s {
  double  tm;
#if NASD_IDLE_SUPPORT > 0
  double  idle;
#endif /* NASD_IDLE_SUPPORT > 0 */
} iter_info_t;

iter_info_t *iters;

void usage(void) {
  int i;
  fprintf(stderr, "USAGE: %s [options] servername password\n", progname);
  fprintf(stderr, "  -a identifier (default = root of partition)\n");
  fprintf(stderr, "  -b blocksize (default=%d)\n", DEF_BLOCKSIZE);
  fprintf(stderr, "  -c do bit compare against bit pattern after I/O\n");
  fprintf(stderr, "  -e eject obj first\n");
  fprintf(stderr, "  -f flush obj between iters\n");
  fprintf(stderr, "  -k use kpdev\n");
  fprintf(stderr, "  -l use colocated drive\n");
  fprintf(stderr, "  -M use message queues\n");
  fprintf(stderr, "  -n iterations (default=%d)\n", DEF_NITERS);
  fprintf(stderr, "  -p partnum (default=%d)\n", partnum);
  fprintf(stderr, "  -r skip read\n");
  fprintf(stderr, "  -R do ranged operations\n");
  fprintf(stderr, "  -s security level\n");
  for(i = 0; i <= NASD_MAX_SECURITY_LEVEL; i++) {
    fprintf(stderr, "     %d %s\n", i, nasd_sec_level_string(i));
  }
  fprintf(stderr, "  -w skip write\n");
  fflush(stderr);
  exit(1);
}


nasd_identifier_t get_root_object(void) {
  nasd_security_param_t sp;
  nasd_status_t rc;
  nasd_ctrl_part_info_t ptinfo;

  sp.partnum = partnum;
  sp.actual_protection = protection;
  if(protection) {
    sp.type = NASD_BLACK_KEY;
  }

  rc = nasd_cl_p_ctrl_get_part_info(h, keys.black_key, &sp, NULL,
                                    partnum, &ptinfo);

  if ((rc == NASD_CTRL_ID_CHECK_FAILED) && (ptinfo.ctrl_id == 0)) {
    /* got back a page of zeroes - no such partition */
    fprintf(stderr, "ERROR: partition %d does not exist\n", partnum);
    fflush(stderr);
    exit(1);
  }

  if (rc) {
    fprintf(stderr, "ERROR: got 0x%x (%s) getting partition info\n",
            rc, nasd_error_string(rc));
    fflush(stderr);
    exit(1);
  }

  if (ptinfo.num_obj == 0) {
    fprintf(stderr, "ERROR: partition has no objects!\n");
    fflush(stderr);
    exit(1);
  }

  return ptinfo.first_obj;
}


void do_flush(nasd_identifier_t   nasdid,
              nasd_status_t      *nasd_status,
              nasd_rpc_status_t  *status) {
  nasd_p_flush_obj_dr_args_t args;
  nasd_p_flush_obj_dr_res_t res;
  
  args.in_identifier = nasdid;
  args.in_partnum = partnum;
  nasd_cl_p_flush_obj_dr(h, cookie.key, &sec_param, &cookie.capability,
			 &args, &res, status);
  *nasd_status = res.nasd_status;
}


void do_eject(nasd_identifier_t   nasdid,
              nasd_status_t      *nasd_status,
              nasd_rpc_status_t  *status) {
    nasd_p_eject_obj_dr_args_t args;
    nasd_p_eject_obj_dr_res_t res;

    args.in_identifier = nasdid;
    args.in_partnum = partnum;
    nasd_cl_p_eject_obj_dr(h, cookie.key, &sec_param, &cookie.capability,
                           &args, &res, status);
    *nasd_status = res.nasd_status;
}


void do_write(int                 blocksize,
              int                 num_iters) {
  nasd_p_smpl_op_dr_args_t write_args;
  nasd_p_fastwrite_dr_res_t write_res;
  nasd_rpc_status_t op_status;
  int i, done;
  nasd_offset_t offset;

  double accum_time, d, avg, avg_rate;
  double variance, stddev=0.0;
  nasd_timespec_t diff;
  nasd_timer_t timer;

  done = 0;
  offset = 0;

  if (do_ufs == 0) {
    write_args.in_partnum = partnum;
    write_args.in_identifier = nasdid;
    write_args.in_len = blocksize;  
    
    for (i = 0; i < num_iters; i++) {
      write_args.in_offset = offset;

      if (do_range == 0) {
        NASD_TM_START(&timer);
        nasd_cl_p_write_simple_dr(h, cookie.key, &sec_param,
                                  &cookie.capability,
                                  &write_args, buffer, &write_res,
                                  &op_status);
        NASD_TM_STOP(&timer);
      } else {
        NASD_TM_START(&timer);
        nasd_cl_p_range_write_dr(h, cookie.key, &sec_param,
                                  &cookie.capability,
                                  &write_args, memlist, &write_res,
                                  &op_status);
        NASD_TM_STOP(&timer);
      }

        NASD_TM_ELAPSED_TS(&timer, &diff);
      
      done++;
      
      d = (double)diff.ts_nsec;
      d /= (double)1000000000.0;
      d += (double)diff.ts_sec;
      
      accum_time += d;
      iters[i].tm = d;
      
    offset += blocksize;
    }
  } else {
    ssize_t out_len;
    int fd = open(server_name, O_RDWR);
    NASD_ASSERT(fd != -1);

    if (lseek(fd, 0, SEEK_SET) != 0) { exit(1); }

    for (i = 0; i < num_iters; i++) {
      NASD_TM_START(&timer);
      out_len = write(fd, buffer, blocksize);
      NASD_TM_STOP(&timer);
      NASD_TM_ELAPSED_TS(&timer, &diff);

      NASD_ASSERT(out_len == blocksize);
      
      done++;

      d = (double)diff.ts_nsec;
      d /= (double)1000000000.0;
      d += (double)diff.ts_sec;
      
      accum_time += d;
      iters[i].tm = d;
    }
  }

  NASD_ASSERT(done == num_iters);

  d = (double)done;
  avg = accum_time / d;

  avg_rate = (double)blocksize;
  avg_rate *= (double)done;
  avg_rate /= (double)1024.0;
  avg_rate /= accum_time;

  variance = (double)0.0;
  if (num_iters > 1) {
    d = (double)(num_iters - 1);
    for (i = 0; i < num_iters; i++) {
      variance += (iters[i].tm - avg) * (iters[i].tm - avg) / d;
    }
  }
  stddev = sqrt(variance);

  printf("%f seconds to do %d ops\n", accum_time, done);
  printf("%f seconds average\n", avg);
  printf("%f ops / sec\n", (d/accum_time));
  printf("blocksize = %d\n", blocksize);
  printf("avg rate = %.2f kbytes/sec (%.2f 8k-chunks/sec)\n",
    avg_rate, avg_rate/((double)8.0));
  printf("standard deviation: %f   variance: %.9f\n", stddev, variance);

}


void do_read(int                 blocksize,
             int                 num_iters) {
  nasd_p_smpl_op_dr_args_t read_args;
  nasd_p_fastread_dr_res_t read_res;
  nasd_rpc_status_t op_status;
  int i, done;
  nasd_offset_t offset;
  
  double accum_time, d, avg, avg_rate;
  double variance, stddev=0.0;
  nasd_timespec_t diff;
  nasd_timer_t timer;

  done = 0;
  offset = 0;

  if (do_ufs == 0) {
    read_args.in_partnum = partnum;
    read_args.in_identifier = nasdid;
    read_args.in_len = blocksize;  
    
    for (i = 0; i < num_iters; i++) {
      read_args.in_offset = offset;

      if (do_range == 0) {
        NASD_TM_START(&timer);
        nasd_cl_p_read_simple_dr(h, cookie.key, &sec_param,
                                 &cookie.capability,
                                 &read_args, buffer, &read_res,
                                 &op_status);
        NASD_TM_STOP(&timer);
      } else {
        NASD_TM_START(&timer);
        nasd_cl_p_range_read_dr(h, cookie.key, &sec_param,
                                 &cookie.capability,
                                 &read_args, memlist, &read_res,
                                 &op_status);
        NASD_TM_STOP(&timer);
      }

      NASD_TM_ELAPSED_TS(&timer, &diff);
      
      done++;
      
      d = (double)diff.ts_nsec;
      d /= (double)1000000000.0;
      d += (double)diff.ts_sec;
      
      accum_time += d;
      iters[i].tm = d;
      
      offset += blocksize;
    }
  } else {
        ssize_t out_len;
    int fd = open(server_name, O_RDWR);
    NASD_ASSERT(fd != -1);

    if (lseek(fd, 0, SEEK_SET) != 0) { exit(1); }

    for (i = 0; i < num_iters; i++) {
      NASD_TM_START(&timer);
      out_len = read(fd, buffer, blocksize);
      NASD_TM_STOP(&timer);
      NASD_TM_ELAPSED_TS(&timer, &diff);

      NASD_ASSERT(out_len == blocksize);
      
      done++;

      d = (double)diff.ts_nsec;
      d /= (double)1000000000.0;
      d += (double)diff.ts_sec;
      
      accum_time += d;
      iters[i].tm = d;
    }
  }

  NASD_ASSERT(done == num_iters);
  
  d = (double)done;
  avg = accum_time / d;
 
  avg_rate = (double)blocksize;
  avg_rate *= (double)done;
  avg_rate /= (double)1024.0;
  avg_rate /= accum_time;
  
  variance = (double)0.0;
  if (num_iters > 1) {
    d = (double)(num_iters - 1);
    for (i = 0; i < num_iters; i++) {
      variance += (iters[i].tm - avg) * (iters[i].tm - avg) / d;
    }
  }
  stddev = sqrt(variance);
  
  printf("%f seconds to do %d ops\n", accum_time, done);
  printf("%f seconds average\n", avg);
  printf("%f ops / sec\n", (d/accum_time));
  printf("blocksize = %d\n", blocksize);
  printf("avg rate = %.2f kbytes/sec (%.2f 8k-chunks/sec)\n",
	 avg_rate, avg_rate/((double)8.0));
  printf("standard deviation: %f   variance: %.9f\n", stddev, variance); 
}


int
main(int     argc, char  **argv) {
  nasd_drive_param_kpdev_t kpdev_args;
  int i, rc;
  int sec_level=0;
  char c;
  char *master_password;
  nasd_timespec_t tm;

  progname = argv[0];
  
  bzero((char *)&cookie, sizeof(nasd_cookie_t));
  bzero(&sec_param, sizeof(nasd_security_param_t));

  rc = nasd_mem_init();
  if (rc) {
    fprintf(stderr,
      "ERROR: could not initialize NASD memory subsystem rc=0x%x (%s)\n",
      rc, nasd_error_string(rc));
    exit(1);
  }

  binding_type = NASD_BIND_DEFAULT;
  binding_args = NULL;
  binding_args_len = 0;

  while (nasd_getopt(argc, argv, "a:b:cefklMn:p:rRuws:", &c)) {
    switch(c) {
      case 'a':
        rc = nasd_str_to_nasd_id(nasd_optarg, &nasdid);
        if (rc) {
          fprintf(stderr, "ERROR: \"%s\" is not a valid NASD identifier\n",
            nasd_optarg);
          usage();
        }
        break;
      case 'b':
        if (sscanf(nasd_optarg, "%d", &blocksize) != 1) {
          usage();
        }
        break;
      case 'e':
        eject_obj = 1;
        break;
      case 's':
        if (sscanf(nasd_optarg, "%d", &sec_level) != 1) {
          usage();
        }
        break;
      case 'c':
        do_compare = 1;
        break;
      case 'k':
        if (nondefault_binding)
          usage();
        nondefault_binding = 1;
        binding_type = NASD_BIND_KPDEV_DEFAULT;
        binding_args = &kpdev_args;
        binding_args_len = sizeof(kpdev_args);
        strcpy(kpdev_args.devname, "/dev/nasdkp0");
        break;
      case 'M':
        if (nondefault_binding)
          usage();
        nondefault_binding = 1;
        binding_type = NASD_BIND_MSGQ;
        break;
      case 'f':
        flush_obj = 1;
        break;
      case 'l':
        if (nondefault_binding)
          usage();
        nondefault_binding = 1;
        binding_type = NASD_BIND_COLOCATE;
        binding_args = &kpdev_args;
        binding_args_len = sizeof(kpdev_args);
        strcpy(kpdev_args.devname, "/dev/nasdkp0");
        break;
      case 'n':
        if (sscanf(nasd_optarg, "%d", &niters) != 1) {
          usage();
        }
        break;
      case 'p':
        if (sscanf(nasd_optarg, "%d", &partnum) != 1) {
          usage();
        }
        break;
      case 'r':
        skip_read = 1;
        break;
      case 'R':
        do_range = 1;
        break;
      case 'u':
        do_ufs = 1;
        break;
      case 'w':
        skip_write = 1;
        break;
      default:
        usage();
    }
  }

  if (nasd_optind >= argc)
    usage();
  server_name = argv[nasd_optind];
  nasd_optind++;

  if (nasd_optind >= argc)
    usage();
  master_password=argv[nasd_optind];
  nasd_optind++;

  nasd_sec_password_to_keys(master_password, partnum, &keys);

  if (nasd_optind < argc)
    usage();

  rc = nasd_sec_seclevel_to_protection(sec_level, &protection);
  if(rc) {
    fprintf(stderr, "Unknown security level %d\n", sec_level);
    usage();
  }
  printf("Security Level %d   protection 0x%x\n", sec_level, protection);

  if (blocksize < 1) {
    fprintf(stderr, "Bad blocksize %d\n", blocksize);
    usage();
  }
  if (niters < 1) {
    fprintf(stderr, "Bad niters %d\n", niters);
    usage();
  }

  NASD_Malloc(iters, niters*sizeof(iter_info_t), (iter_info_t *));
  if (iters == NULL) {
    fprintf(stderr, "ERROR: unable to allocate iteration info array\n");
    fflush(stderr);
    exit(1);
  } 

  /* we allocate an aligned buffer so we can read/write raw devices in the
     UFS case */
  if (do_range == 0) {
    NASD_Valloc(buffer, blocksize, (char *));
    if (buffer == NULL) {
      fprintf(stderr, "ERROR: unable to allocate buffer\n");
      fflush(stderr);
      exit(1);
    }
    
    for(i=0;i<blocksize;i++) { buffer[i] = 'X'; }

  } else {
    int remaining = blocksize, chunk, j;
    char *temp_buf;

    list_len = (blocksize/CHUNK_SIZE) + (((blocksize%CHUNK_SIZE)!=0)?1:0);
    NASD_Malloc(memlist, list_len * sizeof(nasd_mem_list_t),
                (nasd_mem_list_t *));
    if (memlist == NULL) {
      fprintf(stderr, "ERROR: unable to allocate memlist\n");
      fflush(stderr);
      exit(1);
    }

    for (i = 0; i < list_len; i++) {
      chunk = (remaining > CHUNK_SIZE) ? CHUNK_SIZE : remaining;
      remaining -= chunk;

      NASD_Malloc(temp_buf, chunk, (char *));
      if (temp_buf == NULL) {
        fprintf(stderr, "ERROR: unable to allocate buffer\n");
        fflush(stderr);
        exit(1);
      }
      
      for (j = 0; j < chunk; j++) { temp_buf[j] = 'X'; }

      memlist[i].addr   = temp_buf;
      memlist[i].len    = chunk;
      memlist[i].stride = 0;
      memlist[i].nelem  = 1;

      if (i < (list_len - 1)) { memlist[i].next = &(memlist[i+1]); }
      else                    { memlist[i].next = NULL;            }
    }
  }

  if (do_ufs == 0) {
    rc = nasd_cl_p_init();
    if (rc) {
      printf("ERROR (%s:%d): cannot init client library, rc=0x%x (%s)\n",
	     __FILE__, __LINE__, rc, nasd_error_string(rc));
      return(rc);
    }
    
    rc = nasd_bind_to_drive(server_name, NASD_PDRIVE_PORT,
			    binding_type, binding_args, binding_args_len, &h);
    if (rc) {
      fprintf(stderr, "ERROR: cannot bind to server %s\n", server_name);
      fflush(stderr);
      exit(1);
    }
    
    if (nasdid == NASD_ID_NULL) {
      nasdid = get_root_object();
    }
    
    /* Fill in capability and security param */
    nasd_drive_handle_get_time(h, &tm);
    tm.ts_sec+=(60*60);
    nasd_sec_build_capability(partnum, nasdid,
			      (NASD_ACCESS_RIGHTS_READ |
			       NASD_ACCESS_RIGHTS_WRITE |
			       NASD_ACCESS_RIGHTS_FLUSH |
			       NASD_ACCESS_RIGHTS_EJECT),
			      0, tm.ts_sec,
			      protection,
			      NASD_RED_CAPABILITY,
			      0, blocksize*niters,
			      0, keys.red_key,
			      &cookie);
    sec_param.type = NASD_RED_CAPABILITY;
    sec_param.partnum = partnum;
    sec_param.actual_protection = protection;
  }

  if (!skip_write) {
    printf("Writing...\n");
    do_write(blocksize, niters);
    printf("\n");
  }


  if (!skip_read) {
    printf("Reading...\n");
    do_read(blocksize, niters);
  }

  if (do_ufs == 0) {
    rc = nasd_unbind_drive(&h);
    if (rc) {
      fprintf(stderr, "ERROR: got 0x%x (%s) unbinding drive\n",
	      rc, nasd_error_string(rc));
      exit(1);
    }
    nasd_cl_p_shutdown();
  }

  if (do_range == 0) {
    NASD_Free(iters, niters*sizeof(iter_info_t));
    NASD_Free(buffer, blocksize);
  } else {
    for (i = 0; i < list_len; i++) {
      NASD_Free(memlist[i].addr, memlist[i].len);
    }

    NASD_Free(memlist, list_len * sizeof(nasd_mem_list_t));
  }

  nasd_mem_shutdown();

  exit(0);
}

/* Local Variables:  */
/* indent-tabs-mode: nil */
/* tab-width: 2 */
/* End: */
