Blame src/cachetest.c

Packit cb6d3d
/*
Packit cb6d3d
  Copyright (C) 2014 Robert Kausch <robert.kausch@freac.org>
Packit cb6d3d
  Copyright (C) 2008 Monty <monty@xiph.org>
Packit cb6d3d
Packit cb6d3d
  This program is free software: you can redistribute it and/or modify
Packit cb6d3d
  it under the terms of the GNU General Public License as published by
Packit cb6d3d
  the Free Software Foundation, either version 3 of the License, or
Packit cb6d3d
  (at your option) any later version.
Packit cb6d3d
Packit cb6d3d
  This program is distributed in the hope that it will be useful,
Packit cb6d3d
  but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit cb6d3d
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit cb6d3d
  GNU General Public License for more details.
Packit cb6d3d
Packit cb6d3d
  You should have received a copy of the GNU General Public License
Packit cb6d3d
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
Packit cb6d3d
*/
Packit cb6d3d
Packit cb6d3d
/* we can ask most drives what their various caches' sizes are, but no
Packit cb6d3d
   drive will tell if it caches redbook data.  None should, many do,
Packit cb6d3d
   and there's no way in (eg) MMC/ATAPI to tell a cdrom drive not to
Packit cb6d3d
   cache when accessing audio.  SCSI drives have a FUA facility, but
Packit cb6d3d
   it's not clear how many ignore it.  MMC does specify some cache
Packit cb6d3d
   side effect as part of SET READ AHEAD, but it's not clear we can
Packit cb6d3d
   rely on them.  For that reason, we need to empirically determine
Packit cb6d3d
   cache size and strategy used for reads. */
Packit cb6d3d
Packit cb6d3d
#include <unistd.h>
Packit cb6d3d
#include <string.h>
Packit cb6d3d
#include <stdio.h>
Packit cb6d3d
#include <math.h>
Packit cb6d3d
#include <cdio/paranoia/cdda.h>
Packit cb6d3d
#include <cdio/paranoia/paranoia.h>
Packit cb6d3d
#include <cdio/paranoia/version.h>
Packit cb6d3d
#include "cachetest.h"
Packit cb6d3d
Packit cb6d3d
/* not strictly just seeks, but also recapture and read resume when
Packit cb6d3d
   reading/readahead is suspended and idling */
Packit cb6d3d
#define MIN_SEEK_MS 6
Packit cb6d3d
Packit cb6d3d
#define reportC(...) {if(progress){fprintf(progress, __VA_ARGS__);}	\
Packit cb6d3d
    if(log){fprintf(log, __VA_ARGS__);}}
Packit cb6d3d
#define printC(...) {if(progress){fprintf(progress, __VA_ARGS__);}}
Packit cb6d3d
#define logC(...) {if(log){fprintf(log, __VA_ARGS__);}}
Packit cb6d3d
Packit cb6d3d
static int time_drive(cdrom_drive_t *d, FILE *progress, FILE *log, int lba, int len, int initial_seek){
Packit cb6d3d
  int i,x;
Packit cb6d3d
  int latency=0;
Packit cb6d3d
  double sum=0;
Packit cb6d3d
  double sumsq=0;
Packit cb6d3d
  int sofar;
Packit cb6d3d
Packit cb6d3d
  logC("\n");
Packit cb6d3d
Packit cb6d3d
  for(i=0,sofar=0;sofar
Packit cb6d3d
    int toread = (i==0 && initial_seek?1:len-sofar);
Packit cb6d3d
    int ret;
Packit cb6d3d
    /* first read should also trigger a short seek; one sector so seek duration dominates */
Packit cb6d3d
    if((ret=cdio_cddap_read_timed(d,NULL,lba+sofar,toread,&x))<=0){
Packit cb6d3d
      /* media error! grr!  retry elsewhere */
Packit cb6d3d
      if(ret==-404)return -404;
Packit cb6d3d
      return -1;
Packit cb6d3d
    }
Packit cb6d3d
Packit cb6d3d
    if(x>9999)x=9999;
Packit cb6d3d
    if(x<0)x=0;
Packit cb6d3d
    logC("%d:%d:%d ",lba+sofar,ret,x);
Packit cb6d3d
Packit cb6d3d
    sofar+=ret;
Packit cb6d3d
    if(i || !initial_seek){
Packit cb6d3d
      sum+=x;
Packit cb6d3d
      sumsq+= x*x /(float)ret;
Packit cb6d3d
    }else
Packit cb6d3d
      latency=x;
Packit cb6d3d
  }
Packit cb6d3d
Packit cb6d3d
  /* we count even the upper outliers because the drive is almost
Packit cb6d3d
     certainly reading ahead and that will work itself out as we keep
Packit cb6d3d
     reading to catch up.  Besides-- the tests would rather see too
Packit cb6d3d
     slow a timing than too fast; the timing data is used as an
Packit cb6d3d
     optimization when sleeping. */
Packit cb6d3d
  {
Packit cb6d3d
    double mean = sum/(float)(len-1);
Packit cb6d3d
    double stddev = sqrt( (sumsq/(float)(len-1) - mean*mean));
Packit cb6d3d
Packit cb6d3d
    if(initial_seek){
Packit cb6d3d
      printC("%4dms seek, %.2fms/sec read [%.1fx]",latency,mean,1000./75./mean);
Packit cb6d3d
      logC("\n\tInitial seek latency (%d sectors): %dms",len,latency);
Packit cb6d3d
    }
Packit cb6d3d
Packit cb6d3d
    logC("\n\tAverage read latency: %.2fms/sector (raw speed: %.1fx)",mean,1000./75./mean);
Packit cb6d3d
    logC("\n\tRead latency standard deviation: %.2fms/sector",stddev);
Packit cb6d3d
Packit cb6d3d
    return sum;
Packit cb6d3d
  }
Packit cb6d3d
}
Packit cb6d3d
Packit cb6d3d
static float retime_drive(cdrom_drive_t *d, FILE *progress, FILE *log, int lba, int readahead, float oldmean){
Packit cb6d3d
  int sectors = 2000;
Packit cb6d3d
  int total;
Packit cb6d3d
  float newmean;
Packit cb6d3d
  if(sectors*oldmean > 5000) sectors=5000/oldmean;
Packit cb6d3d
  readahead*=10;
Packit cb6d3d
  readahead/=9;
Packit cb6d3d
  if(readahead>sectors)sectors=readahead;
Packit cb6d3d
Packit cb6d3d
  printC("\bo");
Packit cb6d3d
  logC("\n\tRetiming drive...                               ");
Packit cb6d3d
Packit cb6d3d
  total = time_drive(d,NULL,log,lba,sectors,1);
Packit cb6d3d
  newmean = total/(float)sectors;
Packit cb6d3d
Packit cb6d3d
  logC("\n\tOld mean=%.2fms/sec, New mean=%.2fms/sec\n",oldmean,newmean);
Packit cb6d3d
  printC("\b");
Packit cb6d3d
Packit cb6d3d
  if(newmean>oldmean)return newmean;
Packit cb6d3d
  return oldmean;
Packit cb6d3d
}
Packit cb6d3d
Packit cb6d3d
int analyze_cache(cdrom_drive_t *d, FILE *progress, FILE *log, int speed){
Packit cb6d3d
Packit cb6d3d
  /* Some assumptions about timing:
Packit cb6d3d
Packit cb6d3d
     We can't perform cache determination timing based on looking at
Packit cb6d3d
     average transfer times; on slow setups, the speed of a drive
Packit cb6d3d
     reading sectors via PIO will not be reliably distinguishable from
Packit cb6d3d
     the same drive returning data from the cache via pio.  We need
Packit cb6d3d
     something even more noticable and reliable: the seek time. It is
Packit cb6d3d
     unlikely we'd ever see a seek latency of under ~10ms given the
Packit cb6d3d
     synchronization requirements of a CD and the maximum possible
Packit cb6d3d
     rotational velocity. A cache hit would always be faster, even
Packit cb6d3d
     with PIO.
Packit cb6d3d
Packit cb6d3d
     Further complicating things, we have to watch the data collection
Packit cb6d3d
     carefully as we're not always going to be on an unloaded system,
Packit cb6d3d
     and we even have to guard against other apps accessing the drive
Packit cb6d3d
     (something that should never happen on purpose, but could happen
Packit cb6d3d
     by accident).  As we know in our testing when seeks should never
Packit cb6d3d
     occur, a sudden seek-sized latency popping up in the middle of a
Packit cb6d3d
     collection is an indication that collection is possibly invalid.
Packit cb6d3d
Packit cb6d3d
     A second cause of 'spurious latency' would be media damage; if
Packit cb6d3d
     we're consistently hitting latency on the same sector during
Packit cb6d3d
     initial collection, may need to move past it. */
Packit cb6d3d
Packit cb6d3d
  int i,j,ret=0,x;
Packit cb6d3d
  int firstsector=-1;
Packit cb6d3d
  int lastsector=-1;
Packit cb6d3d
  int firsttest=-1;
Packit cb6d3d
  int lasttest=-1;
Packit cb6d3d
  int offset;
Packit cb6d3d
  int warn=0;
Packit cb6d3d
  int current=1000;
Packit cb6d3d
  int hi=15000;
Packit cb6d3d
  int cachesize=0;
Packit cb6d3d
  int readahead=0;
Packit cb6d3d
  int rollbehind=0;
Packit cb6d3d
  int cachegran=0;
Packit cb6d3d
  float mspersector=0;
Packit cb6d3d
  if(speed<=0)speed=-1;
Packit cb6d3d
Packit cb6d3d
  reportC("\n=================== Checking drive cache/timing behavior ===================\n");
Packit cb6d3d
  d->error_retry=0;
Packit cb6d3d
Packit cb6d3d
  /* verify the lib and cache analysis match */
Packit cb6d3d
  if(strcmp(LIBCDIO_PARANOIA_VERSION,paranoia_version())){
Packit cb6d3d
    reportC("\nWARNING: cdparanoia application (and thus the cache tests) does not match the"
Packit cb6d3d
	    "\ninstalled (or in use) libcdda_paranoia.so library.  The final verdict of this"
Packit cb6d3d
	    "\ntesting may or may not be accurate for the actual version of the paranoia"
Packit cb6d3d
	    "library.  Continuing anyway...\n\n");
Packit cb6d3d
  }
Packit cb6d3d
Packit cb6d3d
  /* find the longest stretch of available audio data */
Packit cb6d3d
Packit cb6d3d
  for(i=0;i<d->tracks;i++){
Packit cb6d3d
    if(cdda_track_audiop(d,i+1)==1){
Packit cb6d3d
      if(firsttest == -1)
Packit cb6d3d
	firsttest=cdda_track_firstsector(d,i+1);
Packit cb6d3d
      lasttest=cdda_track_lastsector(d,i+1);
Packit cb6d3d
      if(lasttest-firsttest > lastsector-firstsector){
Packit cb6d3d
	firstsector=firsttest;
Packit cb6d3d
	lastsector=lasttest;
Packit cb6d3d
      }
Packit cb6d3d
    }else{
Packit cb6d3d
      firsttest=-1;
Packit cb6d3d
      lasttest=-1;
Packit cb6d3d
    }
Packit cb6d3d
  }
Packit cb6d3d
Packit cb6d3d
  if(firstsector==-1){
Packit cb6d3d
    reportC("\n\tNo audio on disc; Cannot determine timing behavior...");
Packit cb6d3d
    return -1;
Packit cb6d3d
  }
Packit cb6d3d
Packit cb6d3d
  /* Dump some initial timing data to give a little context for human
Packit cb6d3d
     eyes.  Take readings ten minutes apart (45000 sectors) and at end of disk. */
Packit cb6d3d
  {
Packit cb6d3d
    int best=0;
Packit cb6d3d
    int bestcount=0;
Packit cb6d3d
    int iterating=0;
Packit cb6d3d
Packit cb6d3d
    offset = lastsector-firstsector-current-1;
Packit cb6d3d
Packit cb6d3d
    reportC("\nSeek/read timing:\n");
Packit cb6d3d
Packit cb6d3d
    while(offset>=firstsector){
Packit cb6d3d
      int m = offset/4500;
Packit cb6d3d
      int s = (offset-m*4500)/75;
Packit cb6d3d
      int f = offset-m*4500-s*75;
Packit cb6d3d
      int sofar;
Packit cb6d3d
Packit cb6d3d
      if(iterating){
Packit cb6d3d
	reportC("\n");
Packit cb6d3d
      }else{
Packit cb6d3d
	printC("\r");
Packit cb6d3d
	logC("\n");
Packit cb6d3d
      }
Packit cb6d3d
      reportC("\t[%02d:%02d.%02d]: ",m,s,f);
Packit cb6d3d
Packit cb6d3d
      /* initial seek to put at at a small offset past end of upcoming reads */
Packit cb6d3d
      if((ret=cdda_read(d,NULL,offset+current+1,1))<0){
Packit cb6d3d
	/* media error! grr!  retry elsewhere */
Packit cb6d3d
	if(ret==-404)return -1;
Packit cb6d3d
	reportC("\n\tWARNING: media error during read; continuing at next offset...");
Packit cb6d3d
	offset = (offset-firstsector+44999)/45000*45000+firstsector;
Packit cb6d3d
	offset-=45000;
Packit cb6d3d
	continue;
Packit cb6d3d
      }
Packit cb6d3d
Packit cb6d3d
      sofar=time_drive(d,progress, log, offset, current, 1);
Packit cb6d3d
      if(offset==firstsector)mspersector = sofar/(float)current;
Packit cb6d3d
      if(sofar==-404)
Packit cb6d3d
	return -1;
Packit cb6d3d
      else if(sofar<0){
Packit cb6d3d
	reportC("\n\tWARNING: media error during read; continuing at next offset...");
Packit cb6d3d
	offset = (offset-firstsector+44999)/45000*45000+firstsector;
Packit cb6d3d
	offset-=45000;
Packit cb6d3d
	continue;
Packit cb6d3d
      }else{
Packit cb6d3d
	if(!iterating){
Packit cb6d3d
	  if(best==0 || sofar*1.01
Packit cb6d3d
	    best= sofar;
Packit cb6d3d
	    bestcount=0;
Packit cb6d3d
	  }else{
Packit cb6d3d
	    bestcount+=sofar;
Packit cb6d3d
	    if(bestcount>sofar && bestcount>4000)
Packit cb6d3d
	      iterating=1;
Packit cb6d3d
	  }
Packit cb6d3d
	}
Packit cb6d3d
      }
Packit cb6d3d
Packit cb6d3d
      if(iterating){
Packit cb6d3d
	offset = (offset-firstsector+44999)/45000*45000+firstsector;
Packit cb6d3d
	offset-=45000;
Packit cb6d3d
	printC("                 ");
Packit cb6d3d
      }else{
Packit cb6d3d
	offset--;
Packit cb6d3d
	printC(" spinning up...  ");
Packit cb6d3d
      }
Packit cb6d3d
    }
Packit cb6d3d
  }
Packit cb6d3d
Packit cb6d3d
  reportC("\n\nAnalyzing cache behavior...\n");
Packit cb6d3d
Packit cb6d3d
  /* search on cache size; cache hits are fast, seeks are not, so a
Packit cb6d3d
     linear search through cache hits up to a miss are faster than a
Packit cb6d3d
     bisection */
Packit cb6d3d
  {
Packit cb6d3d
    int under=1;
Packit cb6d3d
    int onex=0;
Packit cb6d3d
    current=0;
Packit cb6d3d
    offset = firstsector+10;
Packit cb6d3d
Packit cb6d3d
    while(current <= hi && under){
Packit cb6d3d
      int i,j;
Packit cb6d3d
      under=0;
Packit cb6d3d
      current++;
Packit cb6d3d
Packit cb6d3d
      if(onex){
Packit cb6d3d
	if(speed==-1){
Packit cb6d3d
	  logC("\tAttempting to reset read speed to full... ");
Packit cb6d3d
	}else{
Packit cb6d3d
	  logC("\tAttempting to reset read speed to %dx... ",speed);
Packit cb6d3d
	}
Packit cb6d3d
	if(cdda_speed_set(d,speed)){
Packit cb6d3d
	  logC("failed.\n");
Packit cb6d3d
	}else{
Packit cb6d3d
	  logC("drive said OK\n");
Packit cb6d3d
	}
Packit cb6d3d
	onex=0;
Packit cb6d3d
      }
Packit cb6d3d
Packit cb6d3d
      printC("\r");
Packit cb6d3d
      reportC("\tFast search for approximate cache size... %d sectors            ",current-1);
Packit cb6d3d
      logC("\n");
Packit cb6d3d
Packit cb6d3d
      for(i=0;i<25 && !under;i++){
Packit cb6d3d
	for(j=0;;j++){
Packit cb6d3d
	  int ret1=0,ret2=0;
Packit cb6d3d
	  if(i>=15){
Packit cb6d3d
	    int sofar=0;
Packit cb6d3d
Packit cb6d3d
	    if(i==15){
Packit cb6d3d
	      printC("\r");
Packit cb6d3d
	      reportC("\tSlow verify for approximate cache size... %d sectors",current-1);
Packit cb6d3d
	      logC("\n");
Packit cb6d3d
Packit cb6d3d
	      logC("\tAttempting to reduce read speed to 1x... ");
Packit cb6d3d
	      if(cdda_speed_set(d,1)){
Packit cb6d3d
		logC("failed.\n");
Packit cb6d3d
	      }else{
Packit cb6d3d
		logC("drive said OK\n");
Packit cb6d3d
	      }
Packit cb6d3d
	      onex=1;
Packit cb6d3d
	    }
Packit cb6d3d
	    printC(".");
Packit cb6d3d
	    logC("\t\t>>> ");
Packit cb6d3d
Packit cb6d3d
	    while(sofar
Packit cb6d3d
	      ret1 = cdda_read_timed(d,NULL,offset+sofar,current-sofar,&x);
Packit cb6d3d
	      logC("slow_read=%d:%d:%d ",offset+sofar,ret1,x);
Packit cb6d3d
	      if(ret1<=0)break;
Packit cb6d3d
	      sofar+=ret1;
Packit cb6d3d
	    }
Packit cb6d3d
	  }else{
Packit cb6d3d
	    ret1 = cdda_read_timed(d,NULL,offset+current-1,1,&x);
Packit cb6d3d
	    logC("\t\t>>> fast_read=%d:%d:%d ",offset+current-1,ret1,x);
Packit cb6d3d
Packit cb6d3d
	    /* Some drives are 'easily distracted' when readahead is
Packit cb6d3d
	       running ahead of the read cursor, causing accesses of
Packit cb6d3d
	       the earliest sectors in the cache to show bursty
Packit cb6d3d
	       latency. If there's no seek here after a borderline
Packit cb6d3d
	       long read of the earliest sector in the cache, then the
Packit cb6d3d
	       cache must not have been dumped yet. */
Packit cb6d3d
Packit cb6d3d
	    if (ret==1 && i && x
Packit cb6d3d
	      under=1;
Packit cb6d3d
	      logC("\n");
Packit cb6d3d
	      break;
Packit cb6d3d
	    }
Packit cb6d3d
	  }
Packit cb6d3d
	  ret2 = cdda_read_timed(d,NULL,offset,1,&x);
Packit cb6d3d
	  logC("seek_read=%d:%d:%d\n",offset,ret2,x);
Packit cb6d3d
Packit cb6d3d
	  if(ret1<=0 || ret2<=0){
Packit cb6d3d
	    offset+=current+100;
Packit cb6d3d
	    if(j==10 || offset+current>lastsector){
Packit cb6d3d
	      reportC("\n\tToo many read errors while performing drive cache checks;"
Packit cb6d3d
		      "\n\t  aborting test.\n\n");
Packit cb6d3d
	      return(-1);
Packit cb6d3d
	    }
Packit cb6d3d
	    reportC("\n\tRead error while performing drive cache checks;"
Packit cb6d3d
		    "\n\t  choosing new offset and trying again.\n");
Packit cb6d3d
	  }else{
Packit cb6d3d
	    if(x==-1){
Packit cb6d3d
	      reportC("\n\tTiming error while performing drive cache checks; aborting test.\n");
Packit cb6d3d
	      return(-1);
Packit cb6d3d
	    }else{
Packit cb6d3d
	      if(x
Packit cb6d3d
		under=1;
Packit cb6d3d
	      }
Packit cb6d3d
	      break;
Packit cb6d3d
	    }
Packit cb6d3d
	  }
Packit cb6d3d
	}
Packit cb6d3d
      }
Packit cb6d3d
    }
Packit cb6d3d
  }
Packit cb6d3d
  cachesize=current-1;
Packit cb6d3d
Packit cb6d3d
  printC("\r");
Packit cb6d3d
  if(cachesize==hi){
Packit cb6d3d
    reportC("\tWARNING: Cannot determine drive cache size or behavior!          \n");
Packit cb6d3d
    return 1;
Packit cb6d3d
  }else if(cachesize){
Packit cb6d3d
    reportC("\tApproximate random access cache size: %d sector(s)               \n",cachesize);
Packit cb6d3d
  }else{
Packit cb6d3d
    reportC("\tDrive does not cache nonlinear access                            \n");
Packit cb6d3d
    return 0;
Packit cb6d3d
  }
Packit cb6d3d
Packit cb6d3d
  /* does the readahead cache exceed the maximum Paranoia currently expects? */
Packit cb6d3d
  {
Packit cb6d3d
    cdrom_paranoia *p=paranoia_init(d);
Packit cb6d3d
    if(cachesize > paranoia_cachemodel_size(p,-1)){
Packit cb6d3d
      reportC("\nWARNING: This drive appears to be caching more sectors of\n"
Packit cb6d3d
	      "           readahead than Paranoia can currently handle!\n");
Packit cb6d3d
      warn=1;
Packit cb6d3d
Packit cb6d3d
    }
Packit cb6d3d
    paranoia_free(p);
Packit cb6d3d
  }
Packit cb6d3d
  if(speed==-1){
Packit cb6d3d
    logC("\tAttempting to reset read speed to full... ");
Packit cb6d3d
  }else{
Packit cb6d3d
    logC("\tAttempting to reset read speed to %d... ",speed);
Packit cb6d3d
  }
Packit cb6d3d
  if(cdda_speed_set(d,speed)){
Packit cb6d3d
    logC("failed.\n");
Packit cb6d3d
  }else{
Packit cb6d3d
    logC("drive said OK\n");
Packit cb6d3d
  }
Packit cb6d3d
Packit cb6d3d
  /* This is similar to the Fast search above, but just in case the
Packit cb6d3d
     cache is being tracked as multiple areas that are treated
Packit cb6d3d
     differently if non-contiguous.... */
Packit cb6d3d
  {
Packit cb6d3d
    int seekoff = cachesize*3;
Packit cb6d3d
    int under=0;
Packit cb6d3d
    reportC("\tVerifying that cache is contiguous...");
Packit cb6d3d
Packit cb6d3d
    for(i=0;i<20 && !under;i++){
Packit cb6d3d
      printC(".");
Packit cb6d3d
      for(j=0;;j++){
Packit cb6d3d
	int ret1,ret2;
Packit cb6d3d
Packit cb6d3d
	if(offset+seekoff>lastsector){
Packit cb6d3d
	  reportC("\n\tOut of readable space on CDROM while performing drive checks;"
Packit cb6d3d
		  "\n\t  aborting test.\n\n");
Packit cb6d3d
	  return(-1);
Packit cb6d3d
	}
Packit cb6d3d
Packit cb6d3d
Packit cb6d3d
	ret1 = cdda_read_timed(d,NULL,offset+seekoff,1,&x);
Packit cb6d3d
	logC("\t\t>>> %d:%d:%d ",offset+seekoff,ret1,x);
Packit cb6d3d
	ret2 = cdda_read_timed(d,NULL,offset,1,&x);
Packit cb6d3d
	logC("seek_read:%d:%d:%d\n",offset,ret2,x);
Packit cb6d3d
Packit cb6d3d
	if(ret1<=0 || ret2<=0){
Packit cb6d3d
	  offset+=cachesize+100;
Packit cb6d3d
	  if(j==10){
Packit cb6d3d
	    reportC("\n\tToo many read errors while performing drive cache checks;"
Packit cb6d3d
		    "\n\t  aborting test.\n\n");
Packit cb6d3d
	    return(-1);
Packit cb6d3d
	  }
Packit cb6d3d
	  reportC("\n\tRead error while performing drive cache checks;"
Packit cb6d3d
		  "\n\t  choosing new offset and trying again.\n");
Packit cb6d3d
	}else{
Packit cb6d3d
	  if(x==-1){
Packit cb6d3d
	    reportC("\n\tTiming error while performing drive cache checks; aborting test.\n");
Packit cb6d3d
	    return(-1);
Packit cb6d3d
	  }else{
Packit cb6d3d
	    if(x
Packit cb6d3d
	    break;
Packit cb6d3d
	  }
Packit cb6d3d
	}
Packit cb6d3d
      }
Packit cb6d3d
    }
Packit cb6d3d
    printC("\r");
Packit cb6d3d
    if(under){
Packit cb6d3d
      reportC("\nWARNING: Drive cache does not appear to be contiguous!\n");
Packit cb6d3d
      warn=1;
Packit cb6d3d
    }else{
Packit cb6d3d
      reportC("\tDrive cache tests as contiguous                           \n");
Packit cb6d3d
    }
Packit cb6d3d
  }
Packit cb6d3d
Packit cb6d3d
  /* The readahead cache size ascertained above is likely qualified by
Packit cb6d3d
     background 'rollahead'; that is, the drive's readahead process is
Packit cb6d3d
     often working ahead of our actual linear reads, and if reads stop
Packit cb6d3d
     or are interrupted, readahead continues and overflows the cache.
Packit cb6d3d
     It is also the case that the cache size we determined above is
Packit cb6d3d
     slightly too low because readahead is probably always working
Packit cb6d3d
     ahead of reads.
Packit cb6d3d
Packit cb6d3d
     Determine the rollahead size a few ways (which may disagree:
Packit cb6d3d
     1) Read number of sectors equal to cache size; pause; read backward until seek
Packit cb6d3d
     2) Read sectors equal to cache-rollahead; verify reading back to beginning does not seek
Packit cb6d3d
     3) Read sectors equal to cache; pause; read ahead until seek delay
Packit cb6d3d
  */
Packit cb6d3d
Packit cb6d3d
  {
Packit cb6d3d
    int lower=0;
Packit cb6d3d
    int gran=64;
Packit cb6d3d
    int it=3;
Packit cb6d3d
    int tests=0;
Packit cb6d3d
    int under=1;
Packit cb6d3d
    readahead=0;
Packit cb6d3d
Packit cb6d3d
    while(gran>1 || under){
Packit cb6d3d
      tests++;
Packit cb6d3d
      if(tests>8 && gran<64){
Packit cb6d3d
	gran<<=3;
Packit cb6d3d
	tests=0;
Packit cb6d3d
	it=3;
Packit cb6d3d
      }
Packit cb6d3d
      if(gran && !under){
Packit cb6d3d
	gran>>=3;
Packit cb6d3d
	tests=0;
Packit cb6d3d
	if(gran==1)it=10;
Packit cb6d3d
      }
Packit cb6d3d
Packit cb6d3d
      under=0;
Packit cb6d3d
      readahead=lower+gran;
Packit cb6d3d
Packit cb6d3d
      printC("\r");
Packit cb6d3d
      logC("\n");
Packit cb6d3d
      reportC("\tTesting background readahead past read cursor... %d",readahead);
Packit cb6d3d
      printC("           \b\b\b\b\b\b\b\b\b\b\b");
Packit cb6d3d
      for (i=0;i
Packit cb6d3d
	int sofar=0, ret=0;
Packit cb6d3d
	logC("\n\t\t%d >>> ",i);
Packit cb6d3d
Packit cb6d3d
	while (sofar
Packit cb6d3d
	  ret = cdda_read_timed(d, NULL, offset+sofar,
Packit cb6d3d
				     cachesize-sofar, &x);
Packit cb6d3d
	  if (ret<=0) goto error;
Packit cb6d3d
	  logC("%d:%d:%d ", offset+sofar, ret, x);
Packit cb6d3d
Packit cb6d3d
	  /* Some drives can lose sync and perform an internal resync,
Packit cb6d3d
	     which can also cause readahead to restart.  If we see
Packit cb6d3d
	     seek-like delays during the initial cahe load, retry the
Packit cb6d3d
	     preload. */
Packit cb6d3d
Packit cb6d3d
	  sofar += ret;
Packit cb6d3d
	}
Packit cb6d3d
Packit cb6d3d
	printC(".");
Packit cb6d3d
Packit cb6d3d
	/* what we'd predict is needed to let the readahead process work. */
Packit cb6d3d
	{
Packit cb6d3d
	  int usec=mspersector*(readahead)*(6+i)*200;
Packit cb6d3d
	  int max= 13000*2*readahead; /* corresponds to .5x */
Packit cb6d3d
	  if(usec>max)usec=max;
Packit cb6d3d
	  logC("sleep=%dus ",usec);
Packit cb6d3d
	  usleep(usec);
Packit cb6d3d
	}
Packit cb6d3d
Packit cb6d3d
	/* seek to offset+cachesize+readahead */
Packit cb6d3d
	ret = cdda_read_timed(d,NULL,offset+cachesize+readahead-1,1,&x);
Packit cb6d3d
	if(ret<=0)break;
Packit cb6d3d
	logC("seek=%d:%d:%d",offset+cachesize+readahead-1,ret,x);
Packit cb6d3d
	if(x
Packit cb6d3d
	  under=1;
Packit cb6d3d
	  break;
Packit cb6d3d
	}else if(i%3==1){
Packit cb6d3d
	  /* retime the drive just to be conservative */
Packit cb6d3d
	  mspersector=retime_drive(d, progress, log, offset, readahead, mspersector);
Packit cb6d3d
	}
Packit cb6d3d
      }
Packit cb6d3d
Packit cb6d3d
      if(under)
Packit cb6d3d
	lower=readahead;
Packit cb6d3d
Packit cb6d3d
    }
Packit cb6d3d
    readahead=lower;
Packit cb6d3d
  }
Packit cb6d3d
  logC("\n");
Packit cb6d3d
  printC("\r");
Packit cb6d3d
  if(readahead==0){
Packit cb6d3d
    reportC("\tDrive does not read ahead past read cursor (very strange)     \n");
Packit cb6d3d
  }else{
Packit cb6d3d
    reportC("\tDrive readahead past read cursor: %d sector(s)                \n",readahead);
Packit cb6d3d
  }
Packit cb6d3d
Packit cb6d3d
   reportC("\tTesting cache tail cursor...");
Packit cb6d3d
Packit cb6d3d
  while(1){
Packit cb6d3d
    rollbehind=cachesize;
Packit cb6d3d
Packit cb6d3d
    for(i=0;i<10 && rollbehind;){
Packit cb6d3d
      int sofar=0,ret=0,retry=0;
Packit cb6d3d
      logC("\n\t\t>>> ");
Packit cb6d3d
      printC(".");
Packit cb6d3d
      while(sofar
Packit cb6d3d
	ret = cdda_read_timed(d,NULL,offset+sofar,cachesize-sofar,&x);
Packit cb6d3d
	if(ret<=0)goto error;
Packit cb6d3d
	logC("%d:%d:%d ",offset+sofar,ret,x);
Packit cb6d3d
	sofar+=ret;
Packit cb6d3d
      }
Packit cb6d3d
Packit cb6d3d
      /* Pause what we'd predict is needed to let the readahead process work. */
Packit cb6d3d
      {
Packit cb6d3d
	int usec=mspersector*readahead*1500;
Packit cb6d3d
	logC("\n\t\tsleeping %d microseconds",usec);
Packit cb6d3d
	usleep(usec);
Packit cb6d3d
      }
Packit cb6d3d
Packit cb6d3d
      /* read backwards until we seek */
Packit cb6d3d
      logC("\n\t\t<<< ");
Packit cb6d3d
      sofar=rollbehind;
Packit cb6d3d
      while(sofar>0){
Packit cb6d3d
	sofar--;
Packit cb6d3d
	ret = cdda_read_timed(d,NULL,offset+sofar,1,&x);
Packit cb6d3d
	if(ret<=0)break;
Packit cb6d3d
	logC("%d:%d:%d ",sofar,ret,x);
Packit cb6d3d
	if(x>=MIN_SEEK_MS){
Packit cb6d3d
	  if(rollbehind != sofar+1){
Packit cb6d3d
	    rollbehind=sofar+1;
Packit cb6d3d
	    i=0;
Packit cb6d3d
	  }else{
Packit cb6d3d
	    i++;
Packit cb6d3d
	  }
Packit cb6d3d
	  break;
Packit cb6d3d
	}
Packit cb6d3d
	if(sofar==0)rollbehind=0;
Packit cb6d3d
      }
Packit cb6d3d
Packit cb6d3d
    error:
Packit cb6d3d
      if(ret<=0){
Packit cb6d3d
	offset+=cachesize;
Packit cb6d3d
	retry++;
Packit cb6d3d
	if(retry>10 || offset+cachesize>lastsector){
Packit cb6d3d
	  reportC("\n\tToo many read errors while performing drive cache checks;"
Packit cb6d3d
		  "\n\t  aborting test.\n\n");
Packit cb6d3d
	  return(-1);
Packit cb6d3d
	}
Packit cb6d3d
	reportC("\n\tRead error while performing drive cache checks;"
Packit cb6d3d
		"\n\t  choosing new offset and trying again.\n");
Packit cb6d3d
	continue;
Packit cb6d3d
      }
Packit cb6d3d
    }
Packit cb6d3d
Packit cb6d3d
    /* verify that the drive timing didn't suddenly change */
Packit cb6d3d
    {
Packit cb6d3d
      float newms=retime_drive(d, progress, log, offset, readahead, mspersector);
Packit cb6d3d
      if(newms > mspersector*1.2){
Packit cb6d3d
	mspersector=newms;
Packit cb6d3d
	printC("\r");
Packit cb6d3d
	reportC("\tDrive timing changed during test; retrying...");
Packit cb6d3d
	continue;
Packit cb6d3d
      }
Packit cb6d3d
    }
Packit cb6d3d
    break;
Packit cb6d3d
Packit cb6d3d
  }
Packit cb6d3d
Packit cb6d3d
  logC("\n");
Packit cb6d3d
  printC("\r");
Packit cb6d3d
  if(rollbehind==0){
Packit cb6d3d
    reportC("\tCache tail cursor tied to read cursor                      \n");
Packit cb6d3d
  }else{
Packit cb6d3d
    reportC("\tCache tail rollbehind: %d sector(s)                        \n",rollbehind);
Packit cb6d3d
  }
Packit cb6d3d
  reportC("\tTesting granularity of cache tail");
Packit cb6d3d
Packit cb6d3d
  while(1){
Packit cb6d3d
    cachegran=cachesize+1;
Packit cb6d3d
    for(i=0;i<10 && cachegran;){
Packit cb6d3d
      int sofar=0,ret=0,retry=0;
Packit cb6d3d
      logC("\n\t\t>>> ");
Packit cb6d3d
      printC(".");
Packit cb6d3d
      while(sofar
Packit cb6d3d
	ret = cdda_read_timed(d,NULL,offset+sofar,cachesize-sofar+1,&x);
Packit cb6d3d
	if(ret<=0)goto error2;
Packit cb6d3d
	logC("%d:%d:%d ",offset+sofar,ret,x);
Packit cb6d3d
	sofar+=ret;
Packit cb6d3d
      }
Packit cb6d3d
Packit cb6d3d
      /* Pause what we'd predict is needed to let the readahead process work. */
Packit cb6d3d
      {
Packit cb6d3d
	int usec=mspersector*readahead*1500;
Packit cb6d3d
	logC("\n\t\tsleeping %d microseconds",usec);
Packit cb6d3d
	usleep(usec);
Packit cb6d3d
      }
Packit cb6d3d
Packit cb6d3d
      /* read backwards until we seek */
Packit cb6d3d
      logC("\n\t\t<<< ");
Packit cb6d3d
      sofar=cachegran;
Packit cb6d3d
      while(sofar){
Packit cb6d3d
	sofar--;
Packit cb6d3d
	ret = cdda_read_timed(d,NULL,offset+sofar,1,&x);
Packit cb6d3d
	if(ret<=0)break;
Packit cb6d3d
	logC("%d:%d:%d ",offset+sofar,ret,x);
Packit cb6d3d
	if(x>=MIN_SEEK_MS){
Packit cb6d3d
	  if(cachegran == sofar+1){
Packit cb6d3d
	    i++;
Packit cb6d3d
	  }else{
Packit cb6d3d
	    cachegran=sofar+1;
Packit cb6d3d
	    i=0;
Packit cb6d3d
	  }
Packit cb6d3d
	  break;
Packit cb6d3d
	}
Packit cb6d3d
	if(sofar==0)cachegran=0;
Packit cb6d3d
      }
Packit cb6d3d
Packit cb6d3d
    error2:
Packit cb6d3d
      if(ret<=0){
Packit cb6d3d
	offset+=cachesize;
Packit cb6d3d
	retry++;
Packit cb6d3d
	if(retry>10 || offset+cachesize>lastsector){
Packit cb6d3d
	  reportC("\n\tToo many read errors while performing drive cache checks;"
Packit cb6d3d
		  "\n\t  aborting test.\n\n");
Packit cb6d3d
	  return(-1);
Packit cb6d3d
	}
Packit cb6d3d
	reportC("\n\tRead error while performing drive cache checks;"
Packit cb6d3d
		"\n\t  choosing new offset and trying again.\n");
Packit cb6d3d
	continue;
Packit cb6d3d
      }
Packit cb6d3d
    }
Packit cb6d3d
Packit cb6d3d
    /* verify that the drive timing didn't suddenly change */
Packit cb6d3d
    {
Packit cb6d3d
      float newms=retime_drive(d, progress, log, offset, readahead, mspersector);
Packit cb6d3d
      if(newms > mspersector*1.2){
Packit cb6d3d
	mspersector=newms;
Packit cb6d3d
	printC("\r");
Packit cb6d3d
	reportC("\tDrive timing changed during test; retrying...");
Packit cb6d3d
	continue;
Packit cb6d3d
      }
Packit cb6d3d
    }
Packit cb6d3d
    break;
Packit cb6d3d
Packit cb6d3d
  }
Packit cb6d3d
Packit cb6d3d
  cachegran -= rollbehind;
Packit cb6d3d
Packit cb6d3d
  logC("\n");
Packit cb6d3d
  printC("\r");
Packit cb6d3d
  reportC("\tCache tail granularity: %d sector(s)                      \n",cachegran);
Packit cb6d3d
Packit cb6d3d
Packit cb6d3d
  /* Verify that a read that begins before the cached readahead dumps
Packit cb6d3d
     the entire readahead cache */
Packit cb6d3d
Packit cb6d3d
  /* This is tricky because we can't simply read a one sector back
Packit cb6d3d
     seek, then rely on timing/seeking of subsequent sectors; the
Packit cb6d3d
     drive may well not seek ahead if reading linearly would be faster
Packit cb6d3d
     (and it often will be), and simply reading ahead after the seek
Packit cb6d3d
     and watching timing will be inaccurate because the drive may roll
Packit cb6d3d
     some readahead into the initial seek/read before returning the
Packit cb6d3d
     first block. */
Packit cb6d3d
Packit cb6d3d
  /* we will need to use the timing of reading from media in one form
Packit cb6d3d
     or another and thus need to guard against slow bus transfer times
Packit cb6d3d
     [eg, no DMA] swamping the actual read time from media. */
Packit cb6d3d
Packit cb6d3d
  /* sample cache access for five realtime seconds. */
Packit cb6d3d
  {
Packit cb6d3d
    float cachems;
Packit cb6d3d
    float readms;
Packit cb6d3d
    int readsize = cachesize-rollbehind-8;
Packit cb6d3d
    int retry=0;
Packit cb6d3d
Packit cb6d3d
    if(readsize>cachesize-1)readsize=cachesize-1;
Packit cb6d3d
Packit cb6d3d
    if(readsize<7){
Packit cb6d3d
      reportC("\tCache size (considering rollbehind) too small to test cache speed.\n");
Packit cb6d3d
    }else{
Packit cb6d3d
      reportC("\tTesting cache transfer speed...");
Packit cb6d3d
Packit cb6d3d
      /* cache timing isn't dependent on rotational speed, so get a good
Packit cb6d3d
	 read and then just hammer the cache; we will only need to do it once */
Packit cb6d3d
Packit cb6d3d
      /* we need to time the cache using the most conservative
Packit cb6d3d
	 possible read pattern; many drives will flush cache on *any*
Packit cb6d3d
	 nonlinear access, but not if the read starts from the same
Packit cb6d3d
	 point.  The original cache size verification algo knows this,
Packit cb6d3d
	 and we need to do it the same way here (this the '0' for
Packit cb6d3d
	 'initial_seek' on time_drve */
Packit cb6d3d
Packit cb6d3d
      while(1){
Packit cb6d3d
	int ret=time_drive(d, NULL, log, offset, readsize, 0);
Packit cb6d3d
	if(ret==-404) return -1;
Packit cb6d3d
	if(ret>0)break;
Packit cb6d3d
	retry++;
Packit cb6d3d
	if(retry==10){
Packit cb6d3d
	  reportC("\n\tToo many read errors while performing drive cache checks;"
Packit cb6d3d
		  "\n\t  aborting test.\n\n");
Packit cb6d3d
	  return(-1);
Packit cb6d3d
	}
Packit cb6d3d
	reportC("\n\tRead error while performing drive cache checks;"
Packit cb6d3d
		"\n\t  choosing new offset and trying again.\n");
Packit cb6d3d
      }
Packit cb6d3d
Packit cb6d3d
      {
Packit cb6d3d
	int elapsed=0;
Packit cb6d3d
	int sectors=0;
Packit cb6d3d
	int spinner=0;
Packit cb6d3d
	while(elapsed<5000){
Packit cb6d3d
	  sectors += readsize;
Packit cb6d3d
	  elapsed += time_drive(d, NULL, log, offset, readsize, 0);
Packit cb6d3d
	  spinner = elapsed*5/1000%4;
Packit cb6d3d
	  printC("\b%c",(spinner==0?'o':(spinner==1?'O':(spinner==2?'o':'.'))));
Packit cb6d3d
	}
Packit cb6d3d
	printC("\r");
Packit cb6d3d
	logC("\n");
Packit cb6d3d
	cachems = elapsed/(float)sectors;
Packit cb6d3d
	reportC("\t        Cache read speed: %.2fms/sector [%dx]\n",
Packit cb6d3d
		cachems,(int)(1000./75./cachems));
Packit cb6d3d
      }
Packit cb6d3d
Packit cb6d3d
      if(cachems*3>mspersector){
Packit cb6d3d
	reportC("\tCache access insufficiently faster than media access to\n"
Packit cb6d3d
		"\t\tperform cache backseek tests\n\n");
Packit cb6d3d
      }else{
Packit cb6d3d
Packit cb6d3d
	/* now do the read/backseek combo */
Packit cb6d3d
	reportC("\tTesting that backseek flushes cache...");
Packit cb6d3d
	{
Packit cb6d3d
	  int total=0;
Packit cb6d3d
	  int elapsed=0;
Packit cb6d3d
	  int sectors=0;
Packit cb6d3d
	  int spinner=0;
Packit cb6d3d
	  int retry=0;
Packit cb6d3d
	  while(elapsed<5000 && total<25){ /* don't kill the drive */
Packit cb6d3d
	    int ret;
Packit cb6d3d
	    while(1){
Packit cb6d3d
	      /* need to force seek/flush, but don't kill the drive */
Packit cb6d3d
	      int seekpos = offset+cachesize+20000;
Packit cb6d3d
	      if(seekpos>lastsector-150)seekpos=lastsector-150;
Packit cb6d3d
	      ret=cdda_read(d, NULL, seekpos, 1);
Packit cb6d3d
	      if(ret>0) ret=time_drive(d, NULL, log, offset+1, readsize, 1);
Packit cb6d3d
	      if(ret>=0) ret=time_drive(d, NULL, log, offset, readsize, 1);
Packit cb6d3d
Packit cb6d3d
	      if(ret<=0){
Packit cb6d3d
		retry++;
Packit cb6d3d
		if(retry==10){
Packit cb6d3d
		  reportC("\n\tToo many read errors while performing drive cache checks;"
Packit cb6d3d
			  "\n\t  aborting test.\n\n");
Packit cb6d3d
		  return(-1);
Packit cb6d3d
		}
Packit cb6d3d
		reportC("\n\tRead error while performing drive cache checks; retrying...");
Packit cb6d3d
	      }else
Packit cb6d3d
		break;
Packit cb6d3d
	    }
Packit cb6d3d
Packit cb6d3d
	    sectors += (readsize-1);
Packit cb6d3d
	    elapsed += ret;
Packit cb6d3d
	    total++;
Packit cb6d3d
Packit cb6d3d
	    spinner = elapsed*5/1000%4;
Packit cb6d3d
	    printC("\b%c",(spinner==0?'o':(spinner==1?'O':(spinner==2?'o':'.'))));
Packit cb6d3d
	  }
Packit cb6d3d
Packit cb6d3d
	  printC("\r");
Packit cb6d3d
	  logC("\n");
Packit cb6d3d
	  readms = elapsed/(float)sectors;
Packit cb6d3d
	  reportC("\t        Access speed after backseek: %.2fms/sector [%dx]\n",
Packit cb6d3d
		  readms,(int)(1000./75./readms));
Packit cb6d3d
	  if(readms*2. < mspersector ||
Packit cb6d3d
	     cachems*2. > readms){
Packit cb6d3d
	    reportC("\tWARNING: Read timing after backseek faster than expected!\n"
Packit cb6d3d
		    "\t         It's possible/likely that this drive is not\n"
Packit cb6d3d
		    "\t         flushing the readahead cache on backward seeks!\n\n");
Packit cb6d3d
	    warn=1;
Packit cb6d3d
	  }else{
Packit cb6d3d
	    reportC("\tBackseek flushes the cache as expected\n");
Packit cb6d3d
	  }
Packit cb6d3d
	}
Packit cb6d3d
      }
Packit cb6d3d
    }
Packit cb6d3d
  }
Packit cb6d3d
Packit cb6d3d
  return warn;
Packit cb6d3d
}