<commit_before>'use strict';

var util = require('../util');
var is = require('../is');

var styfn = {};

// a caching layer for property parsing
styfn.parse = function( name, value, propIsBypass, propIsFlat ){
  var argHash = [ name, value, propIsBypass, propIsFlat ].join('$');
  var propCache = this.propCache = this.propCache || {};
  var ret;
  var impl = parseImpl.bind( this );

  if( !(ret = propCache[argHash]) ){
    ret = propCache[argHash] = impl( name, value, propIsBypass, propIsFlat );
  }

  // always need a copy since props are mutated later in their lifecycles
  ret = util.copy( ret );

  ret.value = util.copy( ret.value ); // because it could be an array, e.g. colour

  return ret;
};

// parse a property; return null on invalid; return parsed property otherwise
// fields :
// - name : the name of the property
// - value : the parsed, native-typed value of the property
// - strValue : a string value that represents the property value in valid css
// - bypass : true iff the property is a bypass property
var parseImpl = function( name, value, propIsBypass, propIsFlat ){
  var self = this;

  name = util.camel2dash( name ); // make sure the property name is in dash form (e.g. 'property-name' not 'propertyName')

  var property = self.properties[ name ];
  var passedValue = value;
  var types = self.types;

  if( !property ){ return null; } // return null on property of unknown name
  if( value === undefined || value === null ){ return null; } // can't assign null

  // the property may be an alias
  if( property.alias ){
    property = property.pointsTo;
    name = property.name;
  }

  var valueIsString = is.string(value);
  if( valueIsString ){ // trim the value to make parsing easier
    value = value.trim();
  }

  var type = property.type;
  if( !type ){ return null; } // no type, no luck

  // check if bypass is null or empty string (i.e. indication to delete bypass property)
  if( propIsBypass && (value === '' || value === null) ){
    return {
      name: name,
      value: value,
      bypass: true,
      deleteBypass: true
    };
  }

  // check if value is a function used as a mapper
  if( is.fn(value) ){
    return {
      name: name,
      value: value,
      strValue: 'fn',
      mapped: types.fn,
      bypass: propIsBypass
    };
  }

  // check if value is mapped
  var data, mapData, layoutData, mapLayoutData, scratch, mapScratch;
  if( !valueIsString || propIsFlat ){
    // then don't bother to do the expensive regex checks

  } else if(
    ( data = new RegExp( types.data.regex ).exec( value ) ) ||
    ( layoutData = new RegExp( types.layoutData.regex ).exec( value ) ) ||
    ( scratch = new RegExp( types.scratch.regex ).exec( value ) )
  ){
    if( propIsBypass ){ return false; } // mappers not allowed in bypass

    var mapped;
    if( data ){
      mapped = types.data;
    } else if( layoutData ){
      mapped = types.layoutData;
    } else {
      mapped = types.scratch;
    }

    data = data || layoutData || scratch;

    return {
      name: name,
      value: data,
      strValue: '' + value,
      mapped: mapped,
      field: data[1],
      bypass: propIsBypass
    };

  } else if(
    ( mapData = new RegExp( types.mapData.regex ).exec( value ) ) ||
    ( mapLayoutData = new RegExp( types.mapLayoutData.regex ).exec( value ) ) ||
    ( mapScratch = new RegExp( types.mapScratch.regex ).exec( value ) )
  ){
    if( propIsBypass ){ return false; } // mappers not allowed in bypass
    if( type.multiple ){ return false; } // impossible to map to num

    var mapped;
    if( mapData ){
      mapped = types.mapData;
    } else if( mapLayoutData ){
      mapped = types.mapLayoutData;
    } else {
      mapped = types.mapScratch;
    }

    mapData = mapData || mapLayoutData || mapScratch;

    // we can map only if the type is a colour or a number
    if( !(type.color || type.number) ){ return false; }

    var valueMin = this.parse( name, mapData[4] ); // parse to validate
    if( !valueMin || valueMin.mapped ){ return false; } // can't be invalid or mapped

    var valueMax = this.parse( name, mapData[5] ); // parse to validate
    if( !valueMax || valueMax.mapped ){ return false; } // can't be invalid or mapped

    // check if valueMin and valueMax are the same
    if( valueMin.value === valueMax.value ){
      return false; // can't make much of a mapper without a range

    } else if( type.color ){
      var c1 = valueMin.value;
      var c2 = valueMax.value;

      var same = c1[0] === c2[0] // red
        && c1[1] === c2[1] // green
        && c1[2] === c2[2] // blue
        && ( // optional alpha
          c1[3] === c2[3] // same alpha outright
          || (
            (c1[3] == null || c1[3] === 1) // full opacity for colour 1?
            &&
            (c2[3] == null || c2[3] === 1) // full opacity for colour 2?
          )
        )
      ;

      if( same ){ return false; } // can't make a mapper without a range
    }

    return {
      name: name,
      value: mapData,
      strValue: '' + value,
      mapped: mapped,
      field: mapData[1],
      fieldMin: parseFloat( mapData[2] ), // min & max are numeric
      fieldMax: parseFloat( mapData[3] ),
      valueMin: valueMin.value,
      valueMax: valueMax.value,
      bypass: propIsBypass
    };
  }

  if( type.multiple && !propIsFlat ){
    var vals = valueIsString ? value.split(/\s+/) : [ value ];
    var valArr = vals.map(function( v ){
      var p = self.parse( name, v, propIsBypass, true );

      if( p.pfValue != null ){
        return p.pfValue;
      } else {
        return p.value;
      }
    });

    return {
      name: name,
      value: valArr,
      pfValue: valArr,
      strValue: valArr.join(' '),
      bypass: propIsBypass,
      units: type.number ? type.implicitUnits || 'px' : undefined
    };
  }

  // several types also allow enums
  var checkEnums = function(){
    for( var i = 0; i < type.enums.length; i++ ){
      var en = type.enums[i];

      if( en === value ){
        return {
          name: name,
          value: value,
          strValue: '' + value,
          bypass: propIsBypass
        };
      }
    }

    return null;
  };

  // check the type and return the appropriate object
  if( type.number ){
    var units;
    var implicitUnits = 'px'; // not set => px

    if( type.units ){ // use specified units if set
      units = type.units;
    }

    if( type.implicitUnits ){
      implicitUnits = type.implicitUnits;
    }

    if( !type.unitless ){
      if( valueIsString ){
        var unitsRegex = 'px|em' + (type.allowPercent ? '|\\%' : '');
        if( units ){ unitsRegex = units; } // only allow explicit units if so set
        var match = value.match( '^(' + util.regex.number + ')(' + unitsRegex + ')?' + '$' );

        if( match ){
          value = match[1];
          units = match[2] || implicitUnits;
        }

      } else if( !units || type.implicitUnits ) {
        units = implicitUnits; // implicitly px if unspecified
      }
    }

    value = parseFloat( value );

    // if not a number and enums not allowed, then the value is invalid
    if( isNaN(value) && type.enums === undefined ){
      return null;
    }

    // check if this number type also accepts special keywords in place of numbers
    // (i.e. `left`, `auto`, etc)
    if( isNaN(value) && type.enums !== undefined ){
      value = passedValue;

      return checkEnums();
    }

    // check if value must be an integer
    if( type.integer && !is.integer(value) ){
      return null;
    }

    // check value is within range
    if( (type.min !== undefined && value < type.min)
    || (type.max !== undefined && value > type.max)
    ){
      return null;
    }

    var ret = {
      name: name,
      value: value,
      strValue: '' + value + (units ? units : ''),
      units: units,
      bypass: propIsBypass
    };

    // normalise value in pixels
    if( type.unitless || (units !== 'px' && units !== 'em') ){
      // then pfValue does not apply
    } else {
      ret.pfValue = ( units === 'px' || !units ? (value) : (this.getEmSizeInPixels() * value) );
    }

    // normalise value in ms
    if( units === 'ms' || units === 's' ){
      ret.pfValue = units === 'ms' ? value : 1000 * value;
    }

    // normalise value in rad
    if( units === 'deg' || units === 'rad' ){
      ret.pfValue = units === 'rad' ? value : value * Math.PI/180;
    }

    return ret;

  } else if( type.propList ) {

    var props = [];
    var propsStr = '' + value;

    if( propsStr === 'none' ){
      // leave empty

    } else { // go over each prop

      var propsSplit = propsStr.split(',');
      for( var i = 0; i < propsSplit.length; i++ ){
        var propName = util.trim( propsSplit[i] );

        if( self.properties[propName] ){
          props.push( propName );
        }
      }

      if( props.length === 0 ){ return null; }
    }

    return {
      name: name,
      value: props,
      strValue: props.length === 0 ? 'none' : props.join(', '),
      bypass: propIsBypass
    };

  } else if( type.numberList ){
    var nums = value.split(',');
    var parsedNums = [];

    if( type.evenNumberList && nums.length % 2 !== 0 ){
      return null;
    }

    for( var i = 0; i < nums.length; i++ ){
      var num = parseFloat( nums[i].trim() );

      if( isNaN(num) ){ return null; }

      if( type.min !== undefined && num < type.min ){ return null; }

      if( type.max !== undefined && num > type.max ){ return null; }

      parsedNums.push( num );
    }

    return {
      name: name,
      value: parsedNums,
      strValue: parsedNums.join(', '),
      bypass: propIsBypass
    };

  } else if( type.color ){
    var tuple = util.color2tuple( value );

    if( !tuple ){ return null; }

    return {
      name: name,
      value: tuple,
      strValue: '' + value,
      bypass: propIsBypass,
      roundValue: true
    };

  } else if( type.regex || type.regexes ){

    // first check enums
    if( type.enums ){
      var enumProp = checkEnums();

      if( enumProp ){ return enumProp; }
    }

    var regexes = type.regexes ? type.regexes : [ type.regex ];

    for( var i = 0; i < regexes.length; i++ ){
      var regex = new RegExp( regexes[i] ); // make a regex from the type string
      var m = regex.exec( value );

      if( m ){ // regex matches
        return {
          name: name,
          value: m,
          strValue: '' + value,
          bypass: propIsBypass
        };

      }
    }

    return null; // didn't match any

  } else if( type.string ){
    // just return
    return {
      name: name,
      value: value,
      strValue: '' + value,
      bypass: propIsBypass
    };

  } else if( type.enums ){ // check enums last because it's a combo type in others
    return checkEnums();

  } else {
    return null; // not a type we can handle
  }

};

module.exports = styfn;
<commit_msg>multiprops should accept array values #359<commit_after>'use strict';

var util = require('../util');
var is = require('../is');

var styfn = {};

// a caching layer for property parsing
styfn.parse = function( name, value, propIsBypass, propIsFlat ){
  var argHash = [ name, value, propIsBypass, propIsFlat ].join('$');
  var propCache = this.propCache = this.propCache || {};
  var ret;
  var impl = parseImpl.bind( this );

  if( !(ret = propCache[argHash]) ){
    ret = propCache[argHash] = impl( name, value, propIsBypass, propIsFlat );
  }

  // always need a copy since props are mutated later in their lifecycles
  ret = util.copy( ret );

  ret.value = util.copy( ret.value ); // because it could be an array, e.g. colour

  return ret;
};

// parse a property; return null on invalid; return parsed property otherwise
// fields :
// - name : the name of the property
// - value : the parsed, native-typed value of the property
// - strValue : a string value that represents the property value in valid css
// - bypass : true iff the property is a bypass property
var parseImpl = function( name, value, propIsBypass, propIsFlat ){
  var self = this;

  name = util.camel2dash( name ); // make sure the property name is in dash form (e.g. 'property-name' not 'propertyName')

  var property = self.properties[ name ];
  var passedValue = value;
  var types = self.types;

  if( !property ){ return null; } // return null on property of unknown name
  if( value === undefined || value === null ){ return null; } // can't assign null

  // the property may be an alias
  if( property.alias ){
    property = property.pointsTo;
    name = property.name;
  }

  var valueIsString = is.string(value);
  if( valueIsString ){ // trim the value to make parsing easier
    value = value.trim();
  }

  var type = property.type;
  if( !type ){ return null; } // no type, no luck

  // check if bypass is null or empty string (i.e. indication to delete bypass property)
  if( propIsBypass && (value === '' || value === null) ){
    return {
      name: name,
      value: value,
      bypass: true,
      deleteBypass: true
    };
  }

  // check if value is a function used as a mapper
  if( is.fn(value) ){
    return {
      name: name,
      value: value,
      strValue: 'fn',
      mapped: types.fn,
      bypass: propIsBypass
    };
  }

  // check if value is mapped
  var data, mapData, layoutData, mapLayoutData, scratch, mapScratch;
  if( !valueIsString || propIsFlat ){
    // then don't bother to do the expensive regex checks

  } else if(
    ( data = new RegExp( types.data.regex ).exec( value ) ) ||
    ( layoutData = new RegExp( types.layoutData.regex ).exec( value ) ) ||
    ( scratch = new RegExp( types.scratch.regex ).exec( value ) )
  ){
    if( propIsBypass ){ return false; } // mappers not allowed in bypass

    var mapped;
    if( data ){
      mapped = types.data;
    } else if( layoutData ){
      mapped = types.layoutData;
    } else {
      mapped = types.scratch;
    }

    data = data || layoutData || scratch;

    return {
      name: name,
      value: data,
      strValue: '' + value,
      mapped: mapped,
      field: data[1],
      bypass: propIsBypass
    };

  } else if(
    ( mapData = new RegExp( types.mapData.regex ).exec( value ) ) ||
    ( mapLayoutData = new RegExp( types.mapLayoutData.regex ).exec( value ) ) ||
    ( mapScratch = new RegExp( types.mapScratch.regex ).exec( value ) )
  ){
    if( propIsBypass ){ return false; } // mappers not allowed in bypass
    if( type.multiple ){ return false; } // impossible to map to num

    var mapped;
    if( mapData ){
      mapped = types.mapData;
    } else if( mapLayoutData ){
      mapped = types.mapLayoutData;
    } else {
      mapped = types.mapScratch;
    }

    mapData = mapData || mapLayoutData || mapScratch;

    // we can map only if the type is a colour or a number
    if( !(type.color || type.number) ){ return false; }

    var valueMin = this.parse( name, mapData[4] ); // parse to validate
    if( !valueMin || valueMin.mapped ){ return false; } // can't be invalid or mapped

    var valueMax = this.parse( name, mapData[5] ); // parse to validate
    if( !valueMax || valueMax.mapped ){ return false; } // can't be invalid or mapped

    // check if valueMin and valueMax are the same
    if( valueMin.value === valueMax.value ){
      return false; // can't make much of a mapper without a range

    } else if( type.color ){
      var c1 = valueMin.value;
      var c2 = valueMax.value;

      var same = c1[0] === c2[0] // red
        && c1[1] === c2[1] // green
        && c1[2] === c2[2] // blue
        && ( // optional alpha
          c1[3] === c2[3] // same alpha outright
          || (
            (c1[3] == null || c1[3] === 1) // full opacity for colour 1?
            &&
            (c2[3] == null || c2[3] === 1) // full opacity for colour 2?
          )
        )
      ;

      if( same ){ return false; } // can't make a mapper without a range
    }

    return {
      name: name,
      value: mapData,
      strValue: '' + value,
      mapped: mapped,
      field: mapData[1],
      fieldMin: parseFloat( mapData[2] ), // min & max are numeric
      fieldMax: parseFloat( mapData[3] ),
      valueMin: valueMin.value,
      valueMax: valueMax.value,
      bypass: propIsBypass
    };
  }

  if( type.multiple && !propIsFlat ){
    var vals;

    if( valueIsString ){
      vals = value.split(/\s+/);
    } else if( is.array(value) ){
      vals = value;
    } else {
      vals = [ value ];
    }

    var valArr = vals.map(function( v ){
      var p = self.parse( name, v, propIsBypass, true );

      if( p.pfValue != null ){
        return p.pfValue;
      } else {
        return p.value;
      }
    });

    return {
      name: name,
      value: valArr,
      pfValue: valArr,
      strValue: valArr.join(' '),
      bypass: propIsBypass,
      units: type.number ? type.implicitUnits || 'px' : undefined
    };
  }

  // several types also allow enums
  var checkEnums = function(){
    for( var i = 0; i < type.enums.length; i++ ){
      var en = type.enums[i];

      if( en === value ){
        return {
          name: name,
          value: value,
          strValue: '' + value,
          bypass: propIsBypass
        };
      }
    }

    return null;
  };

  // check the type and return the appropriate object
  if( type.number ){
    var units;
    var implicitUnits = 'px'; // not set => px

    if( type.units ){ // use specified units if set
      units = type.units;
    }

    if( type.implicitUnits ){
      implicitUnits = type.implicitUnits;
    }

    if( !type.unitless ){
      if( valueIsString ){
        var unitsRegex = 'px|em' + (type.allowPercent ? '|\\%' : '');
        if( units ){ unitsRegex = units; } // only allow explicit units if so set
        var match = value.match( '^(' + util.regex.number + ')(' + unitsRegex + ')?' + '$' );

        if( match ){
          value = match[1];
          units = match[2] || implicitUnits;
        }

      } else if( !units || type.implicitUnits ) {
        units = implicitUnits; // implicitly px if unspecified
      }
    }

    value = parseFloat( value );

    // if not a number and enums not allowed, then the value is invalid
    if( isNaN(value) && type.enums === undefined ){
      return null;
    }

    // check if this number type also accepts special keywords in place of numbers
    // (i.e. `left`, `auto`, etc)
    if( isNaN(value) && type.enums !== undefined ){
      value = passedValue;

      return checkEnums();
    }

    // check if value must be an integer
    if( type.integer && !is.integer(value) ){
      return null;
    }

    // check value is within range
    if( (type.min !== undefined && value < type.min)
    || (type.max !== undefined && value > type.max)
    ){
      return null;
    }

    var ret = {
      name: name,
      value: value,
      strValue: '' + value + (units ? units : ''),
      units: units,
      bypass: propIsBypass
    };

    // normalise value in pixels
    if( type.unitless || (units !== 'px' && units !== 'em') ){
      // then pfValue does not apply
    } else {
      ret.pfValue = ( units === 'px' || !units ? (value) : (this.getEmSizeInPixels() * value) );
    }

    // normalise value in ms
    if( units === 'ms' || units === 's' ){
      ret.pfValue = units === 'ms' ? value : 1000 * value;
    }

    // normalise value in rad
    if( units === 'deg' || units === 'rad' ){
      ret.pfValue = units === 'rad' ? value : value * Math.PI/180;
    }

    return ret;

  } else if( type.propList ) {

    var props = [];
    var propsStr = '' + value;

    if( propsStr === 'none' ){
      // leave empty

    } else { // go over each prop

      var propsSplit = propsStr.split(',');
      for( var i = 0; i < propsSplit.length; i++ ){
        var propName = util.trim( propsSplit[i] );

        if( self.properties[propName] ){
          props.push( propName );
        }
      }

      if( props.length === 0 ){ return null; }
    }

    return {
      name: name,
      value: props,
      strValue: props.length === 0 ? 'none' : props.join(', '),
      bypass: propIsBypass
    };

  } else if( type.numberList ){
    var nums = value.split(',');
    var parsedNums = [];

    if( type.evenNumberList && nums.length % 2 !== 0 ){
      return null;
    }

    for( var i = 0; i < nums.length; i++ ){
      var num = parseFloat( nums[i].trim() );

      if( isNaN(num) ){ return null; }

      if( type.min !== undefined && num < type.min ){ return null; }

      if( type.max !== undefined && num > type.max ){ return null; }

      parsedNums.push( num );
    }

    return {
      name: name,
      value: parsedNums,
      strValue: parsedNums.join(', '),
      bypass: propIsBypass
    };

  } else if( type.color ){
    var tuple = util.color2tuple( value );

    if( !tuple ){ return null; }

    return {
      name: name,
      value: tuple,
      strValue: '' + value,
      bypass: propIsBypass,
      roundValue: true
    };

  } else if( type.regex || type.regexes ){

    // first check enums
    if( type.enums ){
      var enumProp = checkEnums();

      if( enumProp ){ return enumProp; }
    }

    var regexes = type.regexes ? type.regexes : [ type.regex ];

    for( var i = 0; i < regexes.length; i++ ){
      var regex = new RegExp( regexes[i] ); // make a regex from the type string
      var m = regex.exec( value );

      if( m ){ // regex matches
        return {
          name: name,
          value: m,
          strValue: '' + value,
          bypass: propIsBypass
        };

      }
    }

    return null; // didn't match any

  } else if( type.string ){
    // just return
    return {
      name: name,
      value: value,
      strValue: '' + value,
      bypass: propIsBypass
    };

  } else if( type.enums ){ // check enums last because it's a combo type in others
    return checkEnums();

  } else {
    return null; // not a type we can handle
  }

};

module.exports = styfn;
<|endoftext|>


<commit_before>function loadData(query) {
  $.getJSON('/drop/ship/' + query.id + "/" + query.rank + ".json", function(obj) {
    var table = $('table');

    items = [];
    $.each(obj.data, function(map, val) {
      items.push({
        quest: map,
        totalCount: val.totalCount,
        sCount: val.rankCount ? val.rankCount[0] : 0,
        aCount: val.rankCount ? val.rankCount[1] : 0,
        bCount: val.rankCount ? val.rankCount[2] : 0,
        hqLvRange: val.hqLv.join(' ~ '),
        dropRate: val.rate
      });
    });
    items.sort(function(a, b) {
      return a.rate - b.rate;
    });

    table.bootstrapTable().bootstrapTable('append', items);

    $('#cache-time').html(obj.generateTime);
    $('#query-count').html(obj.totalCount);
    $('.fixed-table-toolbar > div:nth-of-type(2)')
      .append("查看百科");

    $('.busy-indicator').hide();
    $('div.row.hidden').removeClass('hidden');
  });
}

function questFormatter(value) {
  return '' + value + '';
}

function dropRateFormatter(value) {
    return value + "%";
}
<commit_msg>Fix links in 'drop/ship' pages<commit_after>var rank = '';

function loadData(query) {
  $.getJSON('/drop/ship/' + query.id + "/" + query.rank + ".json", function(obj) {
    var table = $('table');
    rank = query.rank;

    items = [];
    $.each(obj.data, function(map, val) {
      items.push({
        quest: map,
        totalCount: val.totalCount,
        sCount: val.rankCount ? val.rankCount[0] : 0,
        aCount: val.rankCount ? val.rankCount[1] : 0,
        bCount: val.rankCount ? val.rankCount[2] : 0,
        hqLvRange: val.hqLv.join(' ~ '),
        dropRate: val.rate
      });
    });
    items.sort(function(a, b) {
      return a.rate - b.rate;
    });

    table.bootstrapTable().bootstrapTable('append', items);

    $('#cache-time').html(obj.generateTime);
    $('#query-count').html(obj.totalCount);
    $('.fixed-table-toolbar > div:nth-of-type(2)')
      .append("查看百科");

    $('.busy-indicator').hide();
    $('div.row.hidden').removeClass('hidden');
  });
}

function questFormatter(value) {
  var val = value.replace('(Boss)', '').replace('甲', 3).replace('乙', 2).replace('丙', 1).split('-');
  return '' + value + '';
}

function dropRateFormatter(value) {
    return value + "%";
}
<|endoftext|>


<commit_before>/*
 * superslides
 * https://github.com/nicinabox/superslides
 *
 * Copyright (c) 2013 Nic Aitch
 * Licensed under the MIT license.
 */

(function(window, $) {

  var Superslides, plugin = 'superslides';

  Superslides = function(el, options) {
    this.options = $.extend({
      play: false,
      animation_speed: 600,
      animation_easing: 'swing',
      animation: 'slide',
      inherit_width_from: window,
      inherit_height_from: window,
      pagination: true,
      hashchange: false,
      scrollable: true,
      elements: {
        preserve: '.preserve',
        nav: '.slides-navigation',
        container: '.slides-container',
        pagination: '.slides-pagination'
      }
    }, options);

    var that       = this,
        $control   = $('<div>', { "class": 'slides-control' }),
        multiplier = 1;

    this.$el        = $(el);
    this.$container = this.$el.find(this.options.elements.container);

    // Bind this reference
    this.animation     = this.fx[this.options.animation].bind(this);

    this.image.scale   = this.image.scale.bind(this);
    this.image.center  = this.image.center.bind(this);
    this.image.centerX = this.image.centerX.bind(this);
    this.image.centerY = this.image.centerY.bind(this);

    this.pagination.setup      = this.pagination.setup.bind(this);
    this.pagination.addItem    = this.pagination.addItem.bind(this);
    this.pagination.setCurrent = this.pagination.setCurrent.bind(this);
    this.pagination.events     = this.pagination.events.bind(this);

    // Private Methods
    var initialize = function() {
      multiplier = that.findMultiplier();
      that.findPositions();

      that.$control = that.$container.wrap($control).parent('.slides-control');

      that.width  = that.findWidth();
      that.height = that.findHeight();

      setupCss();
      setupChildren();
      setupContainers();
      setupImages();
      that.pagination.setup();

      $(document).on('click', that.options.elements.nav + " a", function(e) {
        e.preventDefault();

        that.stop();
        if ($(this).hasClass('next')) {
          that.animate('next');
        } else {
          that.animate('prev');
        }
      });

      $(window).on('resize', function() {
        setTimeout(function() {
          var $children = that.$container.children();

          that.width  = that.findWidth();
          that.height = that.findHeight();

          $children.css({
            width: that.width,
            left: that.width
          });

          setupContainers();
          setupImages();
        }, 200);
      });

      $(window).on('hashchange', function() {
        var hash = that.parseHash(),
            index = that.upcomingSlide(hash - 1);

        if (index >= 0 && index !== that.current) {
          that.animate(index);
        }
      });

      that.pagination.events();

      that.start();
      return that;
    };

    var setupCss = function() {
      $('body').css({
        margin: 0
      });

      that.$el.css({
        position: 'relative',
        overflow: 'hidden',
        width: '100%'
      });

      that.$control.css({
        position: 'relative',
        transform: 'translate3d(0)',
        height: '100%'
      });

      that.$container.css({
        display: 'none',
        margin: '0',
        padding: '0',
        listStyle: 'none',
        position: 'relative',
        height: '100%'
      });

      that.$container.find('img').not(that.options.elements.preserve).css({
        "-webkit-backface-visibility": 'hidden',
        "-ms-interpolation-mode": 'bicubic',
        "position": 'absolute',
        "left": '0',
        "top": '0',
        "z-index": '-1',
        "max-width": 'none'
      }).removeAttr('width').removeAttr('height');
    };

    var setupChildren = function() {
      var $children = that.$container.children();

      if ($children.is('img')) {
        $children.wrap('<div>');
        $children = that.$container.children();
      }

      $children.css({
        position: 'absolute',
        overflow: 'hidden',
        display: 'none',
        height: '100%',
        width: that.width,
        left: that.width * 2,
        top: 0,
        zIndex: 0
      });
    };

    var setupImages = function() {
      var $images = that.$container.find('img').not(that.options.elements.preserve);
      $images.each(function() {
        that.image.scale(this);
        that.image.center(this);
      });
    };

    var setupContainers = function() {
      $('body').css({
        margin: 0
      });

      that.$el.css({
        height: that.height
      });

      that.$control.css({
        width: that.width * multiplier,
        left: -that.width
      });
    };

    return initialize();
  };

  Superslides.prototype = {
    mobile: (/mobile/i).test(navigator.userAgent),
    findWidth: function() {
      return $(this.options.inherit_width_from).width();
    },
    findHeight: function() {
      return $(this.options.inherit_width_from).height();
    },

    findMultiplier: function() {
      return this.size() === 1 ? 1 : 3;
    },

    upcomingSlide: function(direction) {
      if ((/next/).test(direction)) {
        return this.nextInDom();

      } else if ((/prev/).test(direction)) {
        return this.prevInDom();

      } else if ((/\d/).test(direction)) {
        return +direction;

      } else {
        return 0;
      }
    },

    findPositions: function(current, thisRef) {
      thisRef = thisRef || this;

      if (current === undefined) {
        current = -1;
      }

      thisRef.current = current;
      thisRef.next    = thisRef.nextInDom();
      thisRef.prev    = thisRef.prevInDom();
    },

    nextInDom: function() {
      var index = this.current + 1;

      if (index === this.size()) {
        index = 0;
      }

      return index;
    },

    prevInDom: function() {
      var index = this.current - 1;

      if (index < 0) {
        index = this.size() - 1;
      }

      return index;
    },

    parseHash: function(hash) {
      hash = hash || window.location.hash;
      hash = hash.replace(/^#/, '');
      if (hash) {
        return hash;
      }
    },

    size: function() {
      return this.$container.children().length;
    },

    destroy: function() {
      return this.$el.removeData();
    },

    update: function() {
      this.findPositions(this.current);
      this.$el.trigger('updated.slides');
    },

    stop: function() {
      clearInterval(this.play_id);
      delete this.play_id;

      this.$el.trigger('stopped.slides');
    },

    start: function() {
      var that = this;

      if (that.options.hashchange) {
        $(window).trigger('hashchange');
      } else {
        this.animate();
      }

      if (this.options.play) {
        if (this.play_id) {
          this.stop();
        }

        this.play_id = setInterval(function() {
          that.animate();
        }, this.options.play);
      }

      this.$el.trigger('started.slides');
    },

    animate: function(direction, userCallback) {
      var that = this,
          orientation = {};

      if (this.animating) {
        return;
      }

      this.animating = true;

      if (direction === undefined) {
        direction = 'next';
      }

      orientation.upcoming_slide = this.upcomingSlide(direction);

      if (orientation.upcoming_slide >= this.size()) {
        return;
      }

      orientation.outgoing_slide    = this.current;
      orientation.upcoming_position = this.width * 2;
      orientation.offset            = -orientation.upcoming_position;

      if (direction === 'prev' || direction < orientation.outgoing_slide) {
        orientation.upcoming_position = 0;
        orientation.offset            = 0;
      }

      that.pagination.setCurrent(orientation.upcoming_slide);

      if (that.options.hashchange) {
        window.location.hash = orientation.upcoming_slide + 1;
      }

      this.animation(orientation, function() {
        that.findPositions(orientation.upcoming_slide, that);

        if (typeof userCallback === 'function') {
          userCallback();
        }

        that.animating = false;

        if (that.init) {
          that.$el.trigger('animated.slides');
        } else {
          that.init = true;
          that.$container.css({
            display: 'block'
          });
        }
      });
    }
  };

  Superslides.prototype.image = {
    centerY: function(image) {
      var $img = $(image);

      $img.css({
        top: (this.height - $img.height()) / 2
      });
    },
    centerX: function(image) {
      var $img = $(image);

      $img.css({
        left: (this.width - $img.width()) / 2
      });
    },
    center: function(image) {
      this.image.centerX(image);
      this.image.centerY(image);
    },
    aspectRatio: function(image) {
      if (!image.naturalHeight && !image.naturalWidth) {
        var img = new Image();
        img.src = image.src;
        image.naturalHeight = img.height;
        image.naturalWidth = img.width;
      }

      return image.naturalHeight / image.naturalWidth;
    },
    scale: function(image) {
      var image_aspect_ratio = this.image.aspectRatio(image),
          container_aspect_ratio = this.height / this.width,
          $img = $(image);

      if (container_aspect_ratio > image_aspect_ratio) {
        $img.css({
          height: this.height,
          width: this.height / image_aspect_ratio
        });

      } else {
        $img.css({
          height: this.width * image_aspect_ratio,
          width: this.width
        });
      }
    }
  };

  Superslides.prototype.fx = {
    slide: function(orientation, complete) {
      var that      = this,
          $children = that.$container.children(),
          $target   = $children.eq(orientation.upcoming_slide);

      $target.css({
        left: orientation.upcoming_position,
        display: 'block'
      });

      that.$control.animate({
        left: orientation.offset
      },
      that.options.animation_speed,
      that.options.animation_easing,
      function() {
        if (that.size() > 1) {
          that.$control.css({
            left: -that.width
          });

          $children.eq(orientation.upcoming_slide).css({
            left: that.width,
            zIndex: 2
          });

          if (orientation.outgoing_slide >= 0) {
            $children.eq(orientation.outgoing_slide).css({
              left: that.width,
              display: 'none',
              zIndex: 0
            });
          }
        }

        complete();
      });
    },
    fade: function(orientation, complete) {
      var that = this,
          $children = that.$container.children(),
          $outgoing = $children.eq(orientation.outgoing_slide),
          $target = $children.eq(orientation.upcoming_slide);

      $target.css({
        left: this.width,
        opacity: 1,
        display: 'block'
      });

      if (orientation.outgoing_slide >= 0) {
        $outgoing.animate({
          opacity: 0
        },
        that.options.animation_speed,
        that.options.animation_easing,
        function() {
          if (that.size() > 1) {
            $children.eq(orientation.upcoming_slide).css({
              zIndex: 2
            });

            if (orientation.outgoing_slide >= 0) {
              $children.eq(orientation.outgoing_slide).css({
                opacity: 1,
                display: 'none',
                zIndex: 0
              });
            }
          }

          complete();
        });
      } else {
        $target.css({
          zIndex: 2
        });
        complete();
      }
    }
  };

  Superslides.prototype.pagination = {
    setCurrent: function(i) {
      var $pagination_children = this.$pagination.children();

      $pagination_children.removeClass('current');
      $pagination_children.eq(i)
        .addClass('current');
    },
    addItem: function(i) {
      var index = i + 1;
      var $item = $("<a>", {
        'href': "#" + index,
        'text': index
      });

      $item.appendTo(this.$pagination);
    },
    setup: function() {
      if (!this.options.pagination || this.size() === 1) { return; }

      var $pagination = $("<nav>", {
        'class': this.options.elements.pagination.replace(/^\./, '')
      });

      this.$pagination = $pagination.appendTo(this.$el);

      for (var i = 0; i < this.size(); i++) {
        this.pagination.addItem(i);
      }
    },
    events: function() {
      var that = this;
      if (!this.options.hashchange) {
        $(document).on('click', that.options.elements.pagination + ' a', function(e) {
          e.preventDefault();
          var hash  = that.parseHash(this.hash),
              index = that.upcomingSlide(hash - 1);
          if (index !== that.current) {
            that.animate(index);
          }
        });
      }
    }
  };

  // jQuery plugin definition
  $.fn[plugin] = function (options) {
    return this.each(function() {
      if (!$.data(this, plugin)) {
        $.data(this, plugin, new Superslides(this, options));
        $(this).trigger('init.slides');
      }
    });
  };

  // Prototype's .bind method
  if (!Function.prototype.bind) { // check if native implementation available
    Function.prototype.bind = function(){
      var fn = this, args = Array.prototype.slice.call(arguments),
          object = args.shift();
      return function(){
        return fn.apply(object,
          args.concat(Array.prototype.slice.call(arguments)));
      };
    };
  }

})(this, jQuery);<commit_msg>Add arrow key support<commit_after>/*
 * superslides
 * https://github.com/nicinabox/superslides
 *
 * Copyright (c) 2013 Nic Aitch
 * Licensed under the MIT license.
 */

(function(window, $) {

  var Superslides, plugin = 'superslides';

  Superslides = function(el, options) {
    this.options = $.extend({
      play: false,
      animation_speed: 600,
      animation_easing: 'swing',
      animation: 'slide',
      inherit_width_from: window,
      inherit_height_from: window,
      pagination: true,
      hashchange: false,
      scrollable: true,
      elements: {
        preserve: '.preserve',
        nav: '.slides-navigation',
        container: '.slides-container',
        pagination: '.slides-pagination'
      }
    }, options);

    var that       = this,
        $control   = $('<div>', { "class": 'slides-control' }),
        multiplier = 1;

    this.$el        = $(el);
    this.$container = this.$el.find(this.options.elements.container);

    // Bind this reference
    this.animation     = this.fx[this.options.animation].bind(this);

    this.image.scale   = this.image.scale.bind(this);
    this.image.center  = this.image.center.bind(this);
    this.image.centerX = this.image.centerX.bind(this);
    this.image.centerY = this.image.centerY.bind(this);

    this.pagination.setup      = this.pagination.setup.bind(this);
    this.pagination.addItem    = this.pagination.addItem.bind(this);
    this.pagination.setCurrent = this.pagination.setCurrent.bind(this);
    this.pagination.events     = this.pagination.events.bind(this);

    // Private Methods
    var initialize = function() {
      multiplier = that.findMultiplier();
      that.findPositions();

      that.$control = that.$container.wrap($control).parent('.slides-control');

      that.width  = that.findWidth();
      that.height = that.findHeight();

      setupCss();
      setupChildren();
      setupContainers();
      setupImages();
      that.pagination.setup();

      $(document).on('click', that.options.elements.nav + " a", function(e) {
        e.preventDefault();

        that.stop();
        if ($(this).hasClass('next')) {
          that.animate('next');
        } else {
          that.animate('prev');
        }
      });

      $(document).on('keyup', function(e) {
        if (e.keyCode === 37) {
          that.animate('prev');
        }
        if (e.keyCode === 39) {
          that.animate('next');
        }
      });

      $(window).on('resize', function() {
        setTimeout(function() {
          var $children = that.$container.children();

          that.width  = that.findWidth();
          that.height = that.findHeight();

          $children.css({
            width: that.width,
            left: that.width
          });

          setupContainers();
          setupImages();
        }, 200);
      });

      $(window).on('hashchange', function() {
        var hash = that.parseHash(),
            index = that.upcomingSlide(hash - 1);

        if (index >= 0 && index !== that.current) {
          that.animate(index);
        }
      });

      that.pagination.events();

      that.start();
      return that;
    };

    var setupCss = function() {
      $('body').css({
        margin: 0
      });

      that.$el.css({
        position: 'relative',
        overflow: 'hidden',
        width: '100%'
      });

      that.$control.css({
        position: 'relative',
        transform: 'translate3d(0)',
        height: '100%'
      });

      that.$container.css({
        display: 'none',
        margin: '0',
        padding: '0',
        listStyle: 'none',
        position: 'relative',
        height: '100%'
      });

      that.$container.find('img').not(that.options.elements.preserve).css({
        "-webkit-backface-visibility": 'hidden',
        "-ms-interpolation-mode": 'bicubic',
        "position": 'absolute',
        "left": '0',
        "top": '0',
        "z-index": '-1',
        "max-width": 'none'
      }).removeAttr('width').removeAttr('height');
    };

    var setupChildren = function() {
      var $children = that.$container.children();

      if ($children.is('img')) {
        $children.wrap('<div>');
        $children = that.$container.children();
      }

      $children.css({
        position: 'absolute',
        overflow: 'hidden',
        display: 'none',
        height: '100%',
        width: that.width,
        left: that.width * 2,
        top: 0,
        zIndex: 0
      });
    };

    var setupImages = function() {
      var $images = that.$container.find('img').not(that.options.elements.preserve);
      $images.each(function() {
        that.image.scale(this);
        that.image.center(this);
      });
    };

    var setupContainers = function() {
      $('body').css({
        margin: 0
      });

      that.$el.css({
        height: that.height
      });

      that.$control.css({
        width: that.width * multiplier,
        left: -that.width
      });
    };

    return initialize();
  };

  Superslides.prototype = {
    mobile: (/mobile/i).test(navigator.userAgent),
    findWidth: function() {
      return $(this.options.inherit_width_from).width();
    },
    findHeight: function() {
      return $(this.options.inherit_width_from).height();
    },

    findMultiplier: function() {
      return this.size() === 1 ? 1 : 3;
    },

    upcomingSlide: function(direction) {
      if ((/next/).test(direction)) {
        return this.nextInDom();

      } else if ((/prev/).test(direction)) {
        return this.prevInDom();

      } else if ((/\d/).test(direction)) {
        return +direction;

      } else {
        return 0;
      }
    },

    findPositions: function(current, thisRef) {
      thisRef = thisRef || this;

      if (current === undefined) {
        current = -1;
      }

      thisRef.current = current;
      thisRef.next    = thisRef.nextInDom();
      thisRef.prev    = thisRef.prevInDom();
    },

    nextInDom: function() {
      var index = this.current + 1;

      if (index === this.size()) {
        index = 0;
      }

      return index;
    },

    prevInDom: function() {
      var index = this.current - 1;

      if (index < 0) {
        index = this.size() - 1;
      }

      return index;
    },

    parseHash: function(hash) {
      hash = hash || window.location.hash;
      hash = hash.replace(/^#/, '');
      if (hash) {
        return hash;
      }
    },

    size: function() {
      return this.$container.children().length;
    },

    destroy: function() {
      return this.$el.removeData();
    },

    update: function() {
      this.findPositions(this.current);
      this.$el.trigger('updated.slides');
    },

    stop: function() {
      clearInterval(this.play_id);
      delete this.play_id;

      this.$el.trigger('stopped.slides');
    },

    start: function() {
      var that = this;

      if (that.options.hashchange) {
        $(window).trigger('hashchange');
      } else {
        this.animate();
      }

      if (this.options.play) {
        if (this.play_id) {
          this.stop();
        }

        this.play_id = setInterval(function() {
          that.animate();
        }, this.options.play);
      }

      this.$el.trigger('started.slides');
    },

    animate: function(direction, userCallback) {
      var that = this,
          orientation = {};

      if (this.animating) {
        return;
      }

      this.animating = true;

      if (direction === undefined) {
        direction = 'next';
      }

      orientation.upcoming_slide = this.upcomingSlide(direction);

      if (orientation.upcoming_slide >= this.size()) {
        return;
      }

      orientation.outgoing_slide    = this.current;
      orientation.upcoming_position = this.width * 2;
      orientation.offset            = -orientation.upcoming_position;

      if (direction === 'prev' || direction < orientation.outgoing_slide) {
        orientation.upcoming_position = 0;
        orientation.offset            = 0;
      }

      that.pagination.setCurrent(orientation.upcoming_slide);

      if (that.options.hashchange) {
        window.location.hash = orientation.upcoming_slide + 1;
      }

      this.animation(orientation, function() {
        that.findPositions(orientation.upcoming_slide, that);

        if (typeof userCallback === 'function') {
          userCallback();
        }

        that.animating = false;

        if (that.init) {
          that.$el.trigger('animated.slides');
        } else {
          that.init = true;
          that.$container.css({
            display: 'block'
          });
        }
      });
    }
  };

  Superslides.prototype.image = {
    centerY: function(image) {
      var $img = $(image);

      $img.css({
        top: (this.height - $img.height()) / 2
      });
    },
    centerX: function(image) {
      var $img = $(image);

      $img.css({
        left: (this.width - $img.width()) / 2
      });
    },
    center: function(image) {
      this.image.centerX(image);
      this.image.centerY(image);
    },
    aspectRatio: function(image) {
      if (!image.naturalHeight && !image.naturalWidth) {
        var img = new Image();
        img.src = image.src;
        image.naturalHeight = img.height;
        image.naturalWidth = img.width;
      }

      return image.naturalHeight / image.naturalWidth;
    },
    scale: function(image) {
      var image_aspect_ratio = this.image.aspectRatio(image),
          container_aspect_ratio = this.height / this.width,
          $img = $(image);

      if (container_aspect_ratio > image_aspect_ratio) {
        $img.css({
          height: this.height,
          width: this.height / image_aspect_ratio
        });

      } else {
        $img.css({
          height: this.width * image_aspect_ratio,
          width: this.width
        });
      }
    }
  };

  Superslides.prototype.fx = {
    slide: function(orientation, complete) {
      var that      = this,
          $children = that.$container.children(),
          $target   = $children.eq(orientation.upcoming_slide);

      $target.css({
        left: orientation.upcoming_position,
        display: 'block'
      });

      that.$control.animate({
        left: orientation.offset
      },
      that.options.animation_speed,
      that.options.animation_easing,
      function() {
        if (that.size() > 1) {
          that.$control.css({
            left: -that.width
          });

          $children.eq(orientation.upcoming_slide).css({
            left: that.width,
            zIndex: 2
          });

          if (orientation.outgoing_slide >= 0) {
            $children.eq(orientation.outgoing_slide).css({
              left: that.width,
              display: 'none',
              zIndex: 0
            });
          }
        }

        complete();
      });
    },
    fade: function(orientation, complete) {
      var that = this,
          $children = that.$container.children(),
          $outgoing = $children.eq(orientation.outgoing_slide),
          $target = $children.eq(orientation.upcoming_slide);

      $target.css({
        left: this.width,
        opacity: 1,
        display: 'block'
      });

      if (orientation.outgoing_slide >= 0) {
        $outgoing.animate({
          opacity: 0
        },
        that.options.animation_speed,
        that.options.animation_easing,
        function() {
          if (that.size() > 1) {
            $children.eq(orientation.upcoming_slide).css({
              zIndex: 2
            });

            if (orientation.outgoing_slide >= 0) {
              $children.eq(orientation.outgoing_slide).css({
                opacity: 1,
                display: 'none',
                zIndex: 0
              });
            }
          }

          complete();
        });
      } else {
        $target.css({
          zIndex: 2
        });
        complete();
      }
    }
  };

  Superslides.prototype.pagination = {
    setCurrent: function(i) {
      var $pagination_children = this.$pagination.children();

      $pagination_children.removeClass('current');
      $pagination_children.eq(i)
        .addClass('current');
    },
    addItem: function(i) {
      var index = i + 1;
      var $item = $("<a>", {
        'href': "#" + index,
        'text': index
      });

      $item.appendTo(this.$pagination);
    },
    setup: function() {
      if (!this.options.pagination || this.size() === 1) { return; }

      var $pagination = $("<nav>", {
        'class': this.options.elements.pagination.replace(/^\./, '')
      });

      this.$pagination = $pagination.appendTo(this.$el);

      for (var i = 0; i < this.size(); i++) {
        this.pagination.addItem(i);
      }
    },
    events: function() {
      var that = this;
      if (!this.options.hashchange) {
        $(document).on('click', that.options.elements.pagination + ' a', function(e) {
          e.preventDefault();
          var hash  = that.parseHash(this.hash),
              index = that.upcomingSlide(hash - 1);
          if (index !== that.current) {
            that.animate(index);
          }
        });
      }
    }
  };

  // jQuery plugin definition
  $.fn[plugin] = function (options) {
    return this.each(function() {
      if (!$.data(this, plugin)) {
        $.data(this, plugin, new Superslides(this, options));
        $(this).trigger('init.slides');
      }
    });
  };

  // Prototype's .bind method
  if (!Function.prototype.bind) { // check if native implementation available
    Function.prototype.bind = function(){
      var fn = this, args = Array.prototype.slice.call(arguments),
          object = args.shift();
      return function(){
        return fn.apply(object,
          args.concat(Array.prototype.slice.call(arguments)));
      };
    };
  }

})(this, jQuery);<|endoftext|>


<commit_before>/*jshint asi:true, laxcomma:true, devel:true */

"use strict";

angular.module('gmcadmin.directives', [])
.directive('chartStatsReq', function () {
  return {
    link: function (scope, element, attrs) {
      var chart = null

      var colors = [
        '#48C636'
      , '#1290EE'
      , '#FF3602'
      , '#E8B90A'
      , '#A110E8'
      , '#FF89E0'
      , '#393939'
      ]

      var brighten = function (color) {
        return Highcharts.Color(color).brighten(0.1).get()
      }

      var darken = function (color) {
        return Highcharts.Color(color).brighten(-0.1).get()
      }

      var labelFormatter = function () {
        var label = ''
        if (this.y) {
          label = Highcharts.numberFormat(this.percentage) + '%'
        }
        return label
      }

      var buildSeries = function (stats) {
        var series = [
          {
            name: 'Requests'
          , innerSize: '20%'
          , dataLabels: { distance: -100, color: 'white' }
          , data: [
              { name: 'Get', y: stats.CmdGet, color: colors[0] }
            , { name: 'Set', y: stats.CmdSet, color: colors[1] }
            , { name: 'Delete', y: stats.DeleteMisses + stats.DeleteHits, color: colors[2] }
            , { name: 'Cas', y: stats.CasMisses + stats.CasHits, color: colors[3] }
            , { name: 'Incr/Decr', y: stats.IncrMisses + stats.IncrHits + stats.DecrMisses + stats.DecrHits, color: colors[4] }
            , { name: 'Touch', y: stats.CmdTouch, color: colors[5] }
            , { name: 'Flush', y: stats.CmdFlush, color: colors[6] }
            ]
          , showInLegend: true
          }
        , {
            name: 'Requests'
          , innerSize: '75%'
          , dataLabels: { enabled: false }
          , data: [
              { name: 'Get Hits', y: stats.GetHits, color: brighten(colors[0]) }
            , { name: 'Get Misses', y: stats.GetMisses, color: darken(colors[0]) }
            , { name: 'Set', y: stats.CmdSet, color: colors[1] }
            , { name: 'Delete Hits', y: stats.DeleteHits, color: brighten(colors[2]) }
            , { name: 'Delete Misses', y: stats.DeleteMisses, color: darken(colors[2]) }
            , { name: 'Cas Hits', y: stats.CasHits, color: brighten(colors[3]) }
            , { name: 'Cas Misses', y: stats.CasMisses, color: darken(colors[3]) }
            , { name: 'Incr/Decr Hits', y: stats.IncrHits + stats.DecrHits, color: brighten(colors[4]) }
            , { name: 'Incr/Decr Misses', y: stats.IncrMisses + stats.DecrMisses, color: darken(colors[4]) }
            , { name: 'Touch Hits', y: stats.TouchHits, color: brighten(colors[5]) }
            , { name: 'Touch Misses', y: stats.TouchMisses, color: darken(colors[5]) }
            , { name: 'Flush', y: stats.CmdFlush, color: colors[6] }
            ]
          , showInLegend: false
          }
        ]
        return series
      }

      var buildChart = function (series, el) {
        var chart = new Highcharts.Chart({
          chart: {
            type: 'pie'
          , renderTo: el
          , backgroundColor: null
          , margin: [0, 0, 0, 0]
          }
        , title: { text: null }
        , legend: { backgroundColor: 'white' }
        , credits: { enabled: false }
        , plotOptions: {
            pie: {
              shadow: false
            , dataLabels: { color: '#222222', formatter: labelFormatter }
            }
          , series: {
              point: {
                events: { legendItemClick: function () { return false } }
              }
            }
          }
        , series: series
        })
        return chart
      }

      var updateChart = function (stats) {
        var series = buildSeries(stats)
        if (!chart) {
          chart = buildChart(series, element[0])
        } else {
          for (var i = 0; i < series.length; i++) {
            chart.series[i].setData(series[i].data)
          }
        }
      }

      scope.$watch('serverStats', function (stats) {
        if (stats && Object.keys(stats).length) {
          updateChart(stats)
        }
      })
    }
  }
})
.directive('chartStatsMem', function () {
  return {
    link: function (scope, element, attrs) {
      var chart = null
      var maxbytes = 0

      var colors = [
        '#4968AB'
      , '#C84D64'
      , '#5FA6A9'
      ]

      var labelFormatter = function () {
        var label = ''
        if (this.y) {
          label = Highcharts.numberFormat(this.percentage) + '%'
        }
        return label
      }

      var unit = 'KB'
      var unitResize = function (value) {
        return value > 1024 ? Math.floor(value / 1024) : value
      }

      var buildSeries = function (stats) {
        var series = [
          {
            name: 'Cache Size'
          , innerSize: '20%'
          , dataLabels: { distance: -100, color: 'white' }
          , data: [
              { name: 'Used', y: unitResize(stats.Used), color: colors[0] }
            , { name: 'Wasted', y: unitResize(stats.Wasted), color: colors[1] }
            , { name: 'Free', y: unitResize(stats.Free), color: colors[2] }
            ]
          , showInLegend: true
          }
        ]
        return series
      }

      var buildChart = function (series, el) {
        var chart = new Highcharts.Chart({
          chart: {
            type: 'pie'
          , backgroundColor: null
          , margin: [0, 0, 0, 0]
          , renderTo: el
          }
        , title: { text: null }
        , legend: { backgroundColor: 'white' }
        , credits: { enabled: false }
        , plotOptions: {
            pie: {
              shadow: false
            , dataLabels: { color: '#222222', formatter: labelFormatter }
            , tooltip: { pointFormat: '{series.name}: {point.y} ' + unit + '
' }
            }
          , series: {
              point: {
                events: { legendItemClick: function () { return false } }
              }
            }
          }
        , series: series
        })
        return chart
      }

      var updateChart = function (stats) {
        var series = buildSeries(stats)
        if (!chart) {
          chart = buildChart(series, element[0])
        } else {
          for (var i = 0; i < series.length; i++) {
            chart.series[i].setData(series[i].data)
          }
        }
      }

      scope.$watch('settingsStats', function (stats) {
        if (stats && Object.keys(stats).length) {
          maxbytes = stats.Maxbytes
        }
      })

      scope.$watch('slabsStats', function (slabs) {
        if (slabs && Object.keys(slabs).length) {
          var stats = { Malloced: 0, Used: 0, Wasted: 0, Free: 0 }
          for (var k in slabs) {
            stats.Malloced += slabs[k].Malloced
            stats.Wasted += slabs[k].Wasted
          }
          stats.Used = stats.Malloced - stats.Wasted
          stats.Free = maxbytes - stats.Malloced
          updateChart(stats)
        }
      })
    }
  }
})
.directive('chartStatsSlab', function () {
  return {
    link: function (scope, element, attrs) {
      var chart = null

      var colors = [
        '#48C636'
      , '#1290EE'
      , '#FF3602'
      , '#E8B90A'
      , '#A110E8'
      , '#FF89E0'
      , '#C84D64'
      , '#4968AB'
      ]

      var labelFormatter = function () {
        var label = ''
        if (this.y) {
          label = Highcharts.numberFormat(this.percentage) + '%'
        }
        return label
      }

      var unit = 'KB'
      var unitResize = function (value) {
        return value > 1024 ? Math.floor(value / 1024) : value
      }

      var buildSeries = function (stats) {
        var series = [
          {
            name: 'Memory'
          , innerSize: '20%'
          , dataLabels: {
              distance: -120
            , color: 'white'
            }
          , tooltip: {
              pointFormat: 'Size: {point.y} ' + unit + '
'
            }
          , data: [
              { name: 'Wasted Memory', y: unitResize(stats.Wasted), color: colors[6] }
            , { name: 'Used Memory', y: unitResize(stats.Malloced - stats.Wasted), color: colors[7] }
            ]
          , showInLegend: false
          }
        , {
            name: 'Requests'
          , innerSize: '60%'
          , dataLabels: { distance: -50, color: 'white' }
          , data: [
              { name: 'Get', y: stats.GetHits, color: colors[0] }
            , { name: 'Set', y: stats.CmdSet, color: colors[1] }
            , { name: 'Delete', y: stats.DeleteHits, color: colors[2] }
            , { name: 'Cas', y: stats.CasHits, color: colors[3] }
            , { name: 'Incr/Decr', y: stats.IncrHits + stats.DecrHits, color: colors[4] }
            , { name: 'Touch', y: stats.TouchHits, color: colors[5] }
            ]
          , showInLegend: true
          }
        ]
        return series
      }

      var buildChart = function (series, el) {
        var chart = new Highcharts.Chart({
          chart: {
            type: 'pie'
          , renderTo: el
          , backgroundColor: null
          , margin: [0, 0, 0, 0]
          }
        , title: { text: null }
        , legend: { backgroundColor: 'white' }
        , credits: { enabled: false }
        , plotOptions: {
            pie: {
              shadow: false
            , dataLabels: { color: '#222222', formatter: labelFormatter }
            }
          , series: {
              point: {
                events: { legendItemClick: function () { return false } }
              }
            }
          }
        , series: series
        })
        return chart
      }

      var updateChart = function (stats) {
        var series = buildSeries(stats)
        if (!chart) {
          chart = buildChart(series, element[0])
        } else {
          for (var i = 0; i < series.length; i++) {
            chart.series[i].setData(series[i].data)
          }
        }
      }

      scope.$watch('slabsStats[slabIndex]', function (stats) {
        if (stats && Object.keys(stats).length) {
          updateChart(stats)
        }
      })
    }
  }
})
.directive('chartStatsLive', function () {
  return {
    link: function (scope, element, attrs) {
      var chart = null
      var config = {
        chartType: 'line'
      , plotOptions: {}
      , colors: []
      , dataLabels: []
      , maxValue: null
      , maxLength: 10
      , unit: ''
      , watch: null
      }
      var deregistration = null

      var trySet = function (x, y) {
        if (y) { x = y }
      }

      scope.$watch(attrs.chartStatsLive, function (obj) {
        angular.extend(config, obj)

        var time = (new Date()).getTime()
        var series = []
        for (var i = 0; i < config.dataLabels.length; i++) {
          series.push({
            name: config.dataLabels[i]
          , color: config.colors[i]
          , data: [{ x: time, y: 0 }]
          })
        }

        chart = buildChart(series, element[0])

        if ("function" === typeof deregistration) { deregistration() }
        if (config.watch) {
          deregistration = scope.$watch(config.watch, function (data) {
            updateChart(data)
          })
        }
      })

      var buildChart = function (series, el) {
        Highcharts.setOptions({ global: { useUTC: false } })

        config.plotOptions.series = { marker: { enabled: false } }

        var time = (new Date()).getTime()
        var chart = new Highcharts.Chart({
          chart: {
            type: config.chartType
          , backgroundColor: null
          , height: 240
          , marginTop: 20
          , renderTo: el
          }
        , title: { text: null }
        , legend: {
            backgroundColor: 'white'
          , layout: 'vertical'
          , align: 'left'
          , verticalAlign: 'top'
          , floating: true
          }
        , credits: { enabled: false }
        , tooltip: {
            shared: true
          , valueSuffix: config.unit
          , headerFormat: ''
          }
        , plotOptions: config.plotOptions
        , yAxis: {
            title: { text: null }
          , max: config.maxValue
          , opposite: true
          , gridLineWidth: 0
          }
        , xAxis: {
            type: 'datetime'
          , tickPixelInterval: 150
          }
        , series: series
        })
        return chart
      }

      var updateChart = function (data) {
        var time = (new Date()).getTime()
        for (var i = 0; i < data.length; i++) {
          chart.series[i].addPoint(
            [time, data[i]],
            i === data.length - 1,
            chart.series[i].data.length > config.maxLength
          )
        }
      }

    }
  }
})
<commit_msg>Use `MB` to display memory in charts.<commit_after>/*jshint asi:true, laxcomma:true, devel:true */

"use strict";

angular.module('gmcadmin.directives', [])
.directive('chartStatsReq', function () {
  return {
    link: function (scope, element, attrs) {
      var chart = null

      var colors = [
        '#48C636'
      , '#1290EE'
      , '#FF3602'
      , '#E8B90A'
      , '#A110E8'
      , '#FF89E0'
      , '#393939'
      ]

      var brighten = function (color) {
        return Highcharts.Color(color).brighten(0.1).get()
      }

      var darken = function (color) {
        return Highcharts.Color(color).brighten(-0.1).get()
      }

      var labelFormatter = function () {
        var label = ''
        if (this.y) {
          label = Highcharts.numberFormat(this.percentage) + '%'
        }
        return label
      }

      var buildSeries = function (stats) {
        var series = [
          {
            name: 'Requests'
          , innerSize: '20%'
          , dataLabels: { distance: -100, color: 'white' }
          , data: [
              { name: 'Get', y: stats.CmdGet, color: colors[0] }
            , { name: 'Set', y: stats.CmdSet, color: colors[1] }
            , { name: 'Delete', y: stats.DeleteMisses + stats.DeleteHits, color: colors[2] }
            , { name: 'Cas', y: stats.CasMisses + stats.CasHits, color: colors[3] }
            , { name: 'Incr/Decr', y: stats.IncrMisses + stats.IncrHits + stats.DecrMisses + stats.DecrHits, color: colors[4] }
            , { name: 'Touch', y: stats.CmdTouch, color: colors[5] }
            , { name: 'Flush', y: stats.CmdFlush, color: colors[6] }
            ]
          , showInLegend: true
          }
        , {
            name: 'Requests'
          , innerSize: '75%'
          , dataLabels: { enabled: false }
          , data: [
              { name: 'Get Hits', y: stats.GetHits, color: brighten(colors[0]) }
            , { name: 'Get Misses', y: stats.GetMisses, color: darken(colors[0]) }
            , { name: 'Set', y: stats.CmdSet, color: colors[1] }
            , { name: 'Delete Hits', y: stats.DeleteHits, color: brighten(colors[2]) }
            , { name: 'Delete Misses', y: stats.DeleteMisses, color: darken(colors[2]) }
            , { name: 'Cas Hits', y: stats.CasHits, color: brighten(colors[3]) }
            , { name: 'Cas Misses', y: stats.CasMisses, color: darken(colors[3]) }
            , { name: 'Incr/Decr Hits', y: stats.IncrHits + stats.DecrHits, color: brighten(colors[4]) }
            , { name: 'Incr/Decr Misses', y: stats.IncrMisses + stats.DecrMisses, color: darken(colors[4]) }
            , { name: 'Touch Hits', y: stats.TouchHits, color: brighten(colors[5]) }
            , { name: 'Touch Misses', y: stats.TouchMisses, color: darken(colors[5]) }
            , { name: 'Flush', y: stats.CmdFlush, color: colors[6] }
            ]
          , showInLegend: false
          }
        ]
        return series
      }

      var buildChart = function (series, el) {
        var chart = new Highcharts.Chart({
          chart: {
            type: 'pie'
          , renderTo: el
          , backgroundColor: null
          , margin: [0, 0, 0, 0]
          }
        , title: { text: null }
        , legend: { backgroundColor: 'white' }
        , credits: { enabled: false }
        , plotOptions: {
            pie: {
              shadow: false
            , dataLabels: { color: '#222222', formatter: labelFormatter }
            }
          , series: {
              point: {
                events: { legendItemClick: function () { return false } }
              }
            }
          }
        , series: series
        })
        return chart
      }

      var updateChart = function (stats) {
        var series = buildSeries(stats)
        if (!chart) {
          chart = buildChart(series, element[0])
        } else {
          for (var i = 0; i < series.length; i++) {
            chart.series[i].setData(series[i].data)
          }
        }
      }

      scope.$watch('serverStats', function (stats) {
        if (stats && Object.keys(stats).length) {
          updateChart(stats)
        }
      })
    }
  }
})
.directive('chartStatsMem', function () {
  return {
    link: function (scope, element, attrs) {
      var chart = null
      var maxbytes = 0

      var colors = [
        '#4968AB'
      , '#C84D64'
      , '#5FA6A9'
      ]

      var labelFormatter = function () {
        var label = ''
        if (this.y) {
          label = '' + this.y + unit + ' / ' + Highcharts.numberFormat(this.percentage) + '%'
        }
        return label
      }

      var unit = 'MB'
      var unitResize = function (value) {
        return parseFloat((value / 1024 / 1024).toFixed(2))
      }

      var buildSeries = function (stats) {
        var series = [
          {
            name: 'Cache Size'
          , innerSize: '20%'
          , dataLabels: { distance: -100, color: 'white' }
          , data: [
              { name: 'Used', y: unitResize(stats.Used), color: colors[0] }
            , { name: 'Wasted', y: unitResize(stats.Wasted), color: colors[1] }
            , { name: 'Free', y: unitResize(stats.Free), color: colors[2] }
            ]
          , showInLegend: true
          }
        ]
        return series
      }

      var buildChart = function (series, el) {
        var chart = new Highcharts.Chart({
          chart: {
            type: 'pie'
          , backgroundColor: null
          , margin: [0, 0, 0, 0]
          , renderTo: el
          }
        , title: { text: null }
        , legend: { backgroundColor: 'white' }
        , credits: { enabled: false }
        , plotOptions: {
            pie: {
              shadow: false
            , dataLabels: { color: '#222222', formatter: labelFormatter }
            , tooltip: { pointFormat: '{series.name}: {point.y} ' + unit + '
' }
            }
          , series: {
              point: {
                events: { legendItemClick: function () { return false } }
              }
            }
          }
        , series: series
        })
        return chart
      }

      var updateChart = function (stats) {
        var series = buildSeries(stats)
        if (!chart) {
          chart = buildChart(series, element[0])
        } else {
          for (var i = 0; i < series.length; i++) {
            chart.series[i].setData(series[i].data)
          }
        }
      }

      scope.$watch('settingsStats', function (stats) {
        if (stats && Object.keys(stats).length) {
          maxbytes = stats.Maxbytes
        }
      })

      scope.$watch('slabsStats', function (slabs) {
        if (slabs && Object.keys(slabs).length) {
          var stats = { Malloced: 0, Used: 0, Wasted: 0, Free: 0 }
          for (var k in slabs) {
            stats.Malloced += slabs[k].Malloced
            stats.Wasted += slabs[k].Wasted
          }
          stats.Used = stats.Malloced - stats.Wasted
          stats.Free = maxbytes - stats.Malloced
          updateChart(stats)
        }
      })
    }
  }
})
.directive('chartStatsSlab', function () {
  return {
    link: function (scope, element, attrs) {
      var chart = null

      var colors = [
        '#48C636'
      , '#1290EE'
      , '#FF3602'
      , '#E8B90A'
      , '#A110E8'
      , '#FF89E0'
      , '#C84D64'
      , '#4968AB'
      ]

      var labelFormatter = function () {
        var label = ''
        if (this.y) {
          label = Highcharts.numberFormat(this.percentage) + '%'
        }
        return label
      }

      var unit = 'MB'
      var unitResize = function (value) {
        return parseFloat((value / 1024 / 1024).toFixed(2))
      }

      var buildSeries = function (stats) {
        var series = [
          {
            name: 'Memory'
          , innerSize: '20%'
          , dataLabels: {
              distance: -120
            , color: 'white'
            }
          , tooltip: {
              pointFormat: 'Size: {point.y} ' + unit + '
'
            }
          , data: [
              { name: 'Wasted Memory', y: unitResize(stats.Wasted), color: colors[6] }
            , { name: 'Used Memory', y: unitResize(stats.Malloced - stats.Wasted), color: colors[7] }
            ]
          , showInLegend: false
          }
        , {
            name: 'Requests'
          , innerSize: '60%'
          , dataLabels: { distance: -50, color: 'white' }
          , data: [
              { name: 'Get', y: stats.GetHits, color: colors[0] }
            , { name: 'Set', y: stats.CmdSet, color: colors[1] }
            , { name: 'Delete', y: stats.DeleteHits, color: colors[2] }
            , { name: 'Cas', y: stats.CasHits, color: colors[3] }
            , { name: 'Incr/Decr', y: stats.IncrHits + stats.DecrHits, color: colors[4] }
            , { name: 'Touch', y: stats.TouchHits, color: colors[5] }
            ]
          , showInLegend: true
          }
        ]
        return series
      }

      var buildChart = function (series, el) {
        var chart = new Highcharts.Chart({
          chart: {
            type: 'pie'
          , renderTo: el
          , backgroundColor: null
          , margin: [0, 0, 0, 0]
          }
        , title: { text: null }
        , legend: { backgroundColor: 'white' }
        , credits: { enabled: false }
        , plotOptions: {
            pie: {
              shadow: false
            , dataLabels: { color: '#222222', formatter: labelFormatter }
            }
          , series: {
              point: {
                events: { legendItemClick: function () { return false } }
              }
            }
          }
        , series: series
        })
        return chart
      }

      var updateChart = function (stats) {
        var series = buildSeries(stats)
        if (!chart) {
          chart = buildChart(series, element[0])
        } else {
          for (var i = 0; i < series.length; i++) {
            chart.series[i].setData(series[i].data)
          }
        }
      }

      scope.$watch('slabsStats[slabIndex]', function (stats) {
        if (stats && Object.keys(stats).length) {
          updateChart(stats)
        }
      })
    }
  }
})
.directive('chartStatsLive', function () {
  return {
    link: function (scope, element, attrs) {
      var chart = null
      var config = {
        chartType: 'line'
      , plotOptions: {}
      , colors: []
      , dataLabels: []
      , maxValue: null
      , maxLength: 10
      , unit: ''
      , watch: null
      }
      var deregistration = null

      var trySet = function (x, y) {
        if (y) { x = y }
      }

      scope.$watch(attrs.chartStatsLive, function (obj) {
        angular.extend(config, obj)

        var time = (new Date()).getTime()
        var series = []
        for (var i = 0; i < config.dataLabels.length; i++) {
          series.push({
            name: config.dataLabels[i]
          , color: config.colors[i]
          , data: [{ x: time, y: 0 }]
          })
        }

        chart = buildChart(series, element[0])

        if ("function" === typeof deregistration) { deregistration() }
        if (config.watch) {
          deregistration = scope.$watch(config.watch, function (data) {
            updateChart(data)
          })
        }
      })

      var buildChart = function (series, el) {
        Highcharts.setOptions({ global: { useUTC: false } })

        config.plotOptions.series = { marker: { enabled: false } }

        var time = (new Date()).getTime()
        var chart = new Highcharts.Chart({
          chart: {
            type: config.chartType
          , backgroundColor: null
          , height: 240
          , marginTop: 20
          , renderTo: el
          }
        , title: { text: null }
        , legend: {
            backgroundColor: 'white'
          , layout: 'vertical'
          , align: 'left'
          , verticalAlign: 'top'
          , floating: true
          }
        , credits: { enabled: false }
        , tooltip: {
            shared: true
          , valueSuffix: config.unit
          , headerFormat: ''
          }
        , plotOptions: config.plotOptions
        , yAxis: {
            title: { text: null }
          , max: config.maxValue
          , opposite: true
          , gridLineWidth: 0
          }
        , xAxis: {
            type: 'datetime'
          , tickPixelInterval: 150
          }
        , series: series
        })
        return chart
      }

      var updateChart = function (data) {
        var time = (new Date()).getTime()
        for (var i = 0; i < data.length; i++) {
          chart.series[i].addPoint(
            [time, data[i]],
            i === data.length - 1,
            chart.series[i].data.length > config.maxLength
          )
        }
      }

    }
  }
})
<|endoftext|>


<commit_before>// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var os = require('os');
var fs = require('fs');
var path = require('path');
var setTimeout = require('timers').setTimeout;
var setImmediate = require('timers').setImmediate;

var allocCluster = require('./lib/alloc-cluster');

allocCluster.test('make p2p requests', {
    numPeers: 5
}, function t(cluster, assert) {
    setup(cluster);

    fs.writeFileSync(
        '/tmp/p2p-hosts.json',
        JSON.stringify(cluster.serverHosts), 'utf8'
    );

    var chan = cluster.client.makeSubChannel({
        serviceName: 'server',
        filePath: '/tmp/p2p-hosts.json'
    });

    sendRequests(chan, 100, onResponse);
    function onResponse(err, resps) {
        assert.ifError(err);

        var table = {};
        for (var i = 0; i < resps.length; i++) {
            var addr = resps[i].remoteAddr;

            if (table[addr] === undefined) {
                table[addr] = 1;
            } else {
                table[addr]++;
            }
        }

        for (i = 0; i < cluster.serverHosts.length; i++) {
            assert.equal(table[cluster.serverHosts[i]], 25,
                'expected 25 requests to each downstream');
        }

        fs.unlinkSync('/tmp/p2p-hosts.json');
        assert.end();
    }
});

allocCluster.test.only('changing the peer list', {
    numPeers: 7
}, function t(cluster, assert) {
    setup(cluster);

    cluster.logger.whitelist('info', 'ChannelWatcher: Removing old peer');
    cluster.logger.whitelist('info', 'draining peer');

    var firstPeers = [];
    var secondPeers = [];

    for (var i = 0; i < 4; i++) {
        firstPeers[i] = cluster.serverHosts[i];
        secondPeers[i] = cluster.serverHosts[i + 2];
    }

    fs.writeFileSync(
        '/tmp/p2p-hosts.json',
        JSON.stringify(firstPeers), 'utf8'
    );

    var chan = cluster.client.makeSubChannel({
        serviceName: 'server',
        filePath: '/tmp/p2p-hosts.json',
        refreshInterval: 500,
        minConnections: 4
    });

    sendRequests(chan, 100, onResponse);

    function onResponse(err, resps) {
        assert.ifError(err);

        var table = collectResponses(resps);

        for (i = 0; i < firstPeers.length; i++) {
            assert.equal(table[firstPeers[i]], 25,
                'expected 25 requests to each of first peers');
        }

        fs.writeFileSync(
            '/tmp/p2p-hosts.json',
            JSON.stringify(secondPeers), 'utf8'
        );

        setTimeout(delay, 700);
    }

    function delay() {
        rollingSendRequsets(chan, 100, onResponse2);
    }

    function onResponse2(err, resps) {
        assert.ifError(err);

        var table = collectResponses(resps);
        var total = 0;

        for (i = 0; i < secondPeers.length; i++) {
            var count = table[secondPeers[i]];
            assert.ok(
                count > 12.5 && count < 37.5,
                'expected between 12.5 & 37.5 requests (' + count +
                ') to each of second peers'
            );

            total += table[secondPeers[i]];
        }

        assert.equal(total, 100, 'expected 100 reqs to 4 hosts');

        fs.unlinkSync('/tmp/p2p-hosts.json');
        assert.end();
    }
});

function collectResponses(resps) {
    var table = {};

    for (var i = 0; i < resps.length; i++) {
        var addr = resps[i].remoteAddr;

        if (table[addr] === undefined) {
            table[addr] = 1;
        } else {
            table[addr]++;
        }
    }

    return table;
}

allocCluster.test('add a peer and request', {
    numPeers: 5
}, function t(cluster, assert) {
    setup(cluster);

    var hostsfilePath = path.join(os.tmpdir(), 'channel-watch-test.json');
    fs.writeFileSync(hostsfilePath, JSON.stringify([]));

    var chan = cluster.client.makeSubChannel({
        serviceName: 'server',
        filePath: hostsfilePath,
        refreshInterval: 500,
        requestDefaults: {
            headers: {
                cn: 'client',
                as: 'raw'
            }
        }
    });

    chan.request({
        serviceName: 'server',
        hasNoParent: true
    }).send('echo', 'a', 'b', onResponse);

    function onResponse(err) {
        assert.ok(err, 'expect request error');
        assert.equal(err.type, 'tchannel.no-peer-available',
            'expected no peer available');

        fs.writeFileSync(hostsfilePath, JSON.stringify([cluster.hosts[1]]));

        // wait for watch to reload the file
        setTimeout(function onDelay() {
            chan.request({
                serviceName: 'server',
                hasNoParent: true
            }).send('echo', 'a', 'b', onResponse2);
        }, 700);
    }

    function onResponse2(err, res, arg2, arg3) {
        assert.ifError(err, 'request with peer should not fail');
        assert.equal(res.ok, true, 'response should be ok');
        assert.equal(String(arg2), 'a', 'arg2 should be correct');
        assert.equal(String(arg3), 'b server by 1',
            'arg3 should be correct');
        assert.end();
    }
});

allocCluster.test('duplicate host entry', {
    numPeers: 2
}, function t(cluster, assert) {
    setup(cluster);

    var hostsfilePath = path.join(os.tmpdir(), 'channel-watch-test.json');
    fs.writeFileSync(hostsfilePath, JSON.stringify([
        cluster.hosts[1],
        cluster.hosts[1]
    ]));

    var chan = cluster.client.makeSubChannel({
        serviceName: 'server',
        filePath: hostsfilePath,
        refreshInterval: 500,
        requestDefaults: {
            headers: {
                cn: 'client',
                as: 'raw'
            }
        }
    });

    chan.request({
        serviceName: 'server',
        hasNoParent: true
    }).send('echo', 'a', 'b', onResponse);

    function onResponse(err, res) {
        assert.ifError(err, 'request with peer should not fail');
        assert.equal(res.ok, true, 'response should be ok');

        assert.equal(chan.peers.keys().length, 1,
            'steve should only have one peer');

        assert.end();
    }
});

allocCluster.test('remove a peer and request', {
    numPeers: 2
}, function t(cluster, assert) {
    setup(cluster);

    cluster.logger.whitelist('info', 'ChannelWatcher: Removing old peer');
    cluster.logger.whitelist('info', 'draining peer');

    var hostsfilePath = path.join(os.tmpdir(), 'channel-watch-test.json');
    fs.writeFileSync(hostsfilePath, JSON.stringify([cluster.hosts[1]]));

    var chan = cluster.client.makeSubChannel({
        serviceName: 'server',
        filePath: hostsfilePath,
        refreshInterval: 500,
        requestDefaults: {
            headers: {
                cn: 'client',
                as: 'raw'
            }
        }
    });

    chan.request({
        serviceName: 'server',
        hasNoParent: true
    }).send('echo', 'a', 'b', onResponse);

    function onResponse(err, res, arg2, arg3) {
        assert.ifError(err, 'request with peer should not fail');
        assert.equal(res.ok, true, 'response should be ok');
        assert.equal(String(arg2), 'a', 'arg2 should be correct');
        assert.equal(String(arg3), 'b server by 1', 'arg3 should be correct');

        fs.writeFileSync(hostsfilePath, JSON.stringify([]));

        // wait for watch to reload the file
        setTimeout(function onDelay() {
            chan.request({
                serviceName: 'steve',
                hasNoParent: true
            }).send('echo', 'a', 'b', onResponse2);
        }, 700);
    }

    function onResponse2(err) {
        assert.ok(err, 'expect request error');
        assert.equal(err.type, 'tchannel.no-peer-available',
            'expected no peer available');

        assert.end();
    }
});

function sendRequests(channel, count, cb) {
    var responses = [];

    for (var i = 0; i < count; i++) {
        channel.request({
            serviceName: 'server',
            hasNoParent: true,
            timeout: 500,
            headers: {
                cn: 'client',
                as: 'raw'
            }
        }).send('echo', 'a', 'body', onResponse);
    }

    function onResponse(err, resp) {
        if (err) {
            if (cb !== null) {
                cb(err);
                cb = null;
            }
            return;
        }

        responses.push(resp);
        if (responses.length === count) {
            cb(null, responses);
        }
    }
}

function rollingSendRequsets(channel, count, cb) {
    var responses = [];

    var index = 0;

    setImmediate(fireOnce);

    function fireOnce() {
        channel.request({
            serviceName: 'server',
            hasNoParent: true,
            timeout: 500,
            headers: {
                cn: 'client',
                as: 'raw'
            }
        }).send('echo', 'a', 'body', onResponse);

        index++;

        if (index < count) {
            setImmediate(fireOnce);
        }
    }

    function onResponse(err, resp) {
        if (err) {
            if (cb !== null) {
                cb(err);
                cb = null;
            }
            return;
        }

        responses.push(resp);
        if (responses.length === count) {
            cb(null, responses);
        }
    }
}

function setup(cluster) {
    cluster.logger.whitelist(
        'info', 'ChannelWatcher: Loading peer list from file sync'
    );
    cluster.logger.whitelist(
        'info', 'ChannelWatcher: Loaded peers'
    );
    cluster.logger.whitelist(
        'info', 'ChannelWatcher: Loading peer list from file async'
    );

    cluster.client = cluster.channels[0];

    cluster.servers = cluster.channels.slice(1);
    cluster.serverHosts = [];

    for (var i = 0; i < cluster.servers.length; i++) {
        cluster.serverHosts.push(cluster.servers[i].hostPort);
        makeServer(cluster.servers[i], i);
    }
}

function makeServer(channel, index) {
    var chanNum = index + 1;

    var serverChan = channel.makeSubChannel({
        serviceName: 'server'
    });

    serverChan.register('echo', function echo(req, res, head, body) {
        res.headers.as = 'raw';
        res.sendOk(head, body + ' server by ' + chanNum);
    });
}
<commit_msg>remove accidental leftover only()<commit_after>// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var os = require('os');
var fs = require('fs');
var path = require('path');
var setTimeout = require('timers').setTimeout;
var setImmediate = require('timers').setImmediate;

var allocCluster = require('./lib/alloc-cluster');

allocCluster.test('make p2p requests', {
    numPeers: 5
}, function t(cluster, assert) {
    setup(cluster);

    fs.writeFileSync(
        '/tmp/p2p-hosts.json',
        JSON.stringify(cluster.serverHosts), 'utf8'
    );

    var chan = cluster.client.makeSubChannel({
        serviceName: 'server',
        filePath: '/tmp/p2p-hosts.json'
    });

    sendRequests(chan, 100, onResponse);
    function onResponse(err, resps) {
        assert.ifError(err);

        var table = {};
        for (var i = 0; i < resps.length; i++) {
            var addr = resps[i].remoteAddr;

            if (table[addr] === undefined) {
                table[addr] = 1;
            } else {
                table[addr]++;
            }
        }

        for (i = 0; i < cluster.serverHosts.length; i++) {
            assert.equal(table[cluster.serverHosts[i]], 25,
                'expected 25 requests to each downstream');
        }

        fs.unlinkSync('/tmp/p2p-hosts.json');
        assert.end();
    }
});

allocCluster.test('changing the peer list', {
    numPeers: 7
}, function t(cluster, assert) {
    setup(cluster);

    cluster.logger.whitelist('info', 'ChannelWatcher: Removing old peer');
    cluster.logger.whitelist('info', 'draining peer');

    var firstPeers = [];
    var secondPeers = [];

    for (var i = 0; i < 4; i++) {
        firstPeers[i] = cluster.serverHosts[i];
        secondPeers[i] = cluster.serverHosts[i + 2];
    }

    fs.writeFileSync(
        '/tmp/p2p-hosts.json',
        JSON.stringify(firstPeers), 'utf8'
    );

    var chan = cluster.client.makeSubChannel({
        serviceName: 'server',
        filePath: '/tmp/p2p-hosts.json',
        refreshInterval: 500,
        minConnections: 4
    });

    sendRequests(chan, 100, onResponse);

    function onResponse(err, resps) {
        assert.ifError(err);

        var table = collectResponses(resps);

        for (i = 0; i < firstPeers.length; i++) {
            assert.equal(table[firstPeers[i]], 25,
                'expected 25 requests to each of first peers');
        }

        fs.writeFileSync(
            '/tmp/p2p-hosts.json',
            JSON.stringify(secondPeers), 'utf8'
        );

        setTimeout(delay, 700);
    }

    function delay() {
        rollingSendRequsets(chan, 100, onResponse2);
    }

    function onResponse2(err, resps) {
        assert.ifError(err);

        var table = collectResponses(resps);
        var total = 0;

        for (i = 0; i < secondPeers.length; i++) {
            var count = table[secondPeers[i]];
            assert.ok(
                count > 12.5 && count < 37.5,
                'expected between 12.5 & 37.5 requests (' + count +
                ') to each of second peers'
            );

            total += table[secondPeers[i]];
        }

        assert.equal(total, 100, 'expected 100 reqs to 4 hosts');

        fs.unlinkSync('/tmp/p2p-hosts.json');
        assert.end();
    }
});

function collectResponses(resps) {
    var table = {};

    for (var i = 0; i < resps.length; i++) {
        var addr = resps[i].remoteAddr;

        if (table[addr] === undefined) {
            table[addr] = 1;
        } else {
            table[addr]++;
        }
    }

    return table;
}

allocCluster.test('add a peer and request', {
    numPeers: 5
}, function t(cluster, assert) {
    setup(cluster);

    var hostsfilePath = path.join(os.tmpdir(), 'channel-watch-test.json');
    fs.writeFileSync(hostsfilePath, JSON.stringify([]));

    var chan = cluster.client.makeSubChannel({
        serviceName: 'server',
        filePath: hostsfilePath,
        refreshInterval: 500,
        requestDefaults: {
            headers: {
                cn: 'client',
                as: 'raw'
            }
        }
    });

    chan.request({
        serviceName: 'server',
        hasNoParent: true
    }).send('echo', 'a', 'b', onResponse);

    function onResponse(err) {
        assert.ok(err, 'expect request error');
        assert.equal(err.type, 'tchannel.no-peer-available',
            'expected no peer available');

        fs.writeFileSync(hostsfilePath, JSON.stringify([cluster.hosts[1]]));

        // wait for watch to reload the file
        setTimeout(function onDelay() {
            chan.request({
                serviceName: 'server',
                hasNoParent: true
            }).send('echo', 'a', 'b', onResponse2);
        }, 700);
    }

    function onResponse2(err, res, arg2, arg3) {
        assert.ifError(err, 'request with peer should not fail');
        assert.equal(res.ok, true, 'response should be ok');
        assert.equal(String(arg2), 'a', 'arg2 should be correct');
        assert.equal(String(arg3), 'b server by 1',
            'arg3 should be correct');
        assert.end();
    }
});

allocCluster.test('duplicate host entry', {
    numPeers: 2
}, function t(cluster, assert) {
    setup(cluster);

    var hostsfilePath = path.join(os.tmpdir(), 'channel-watch-test.json');
    fs.writeFileSync(hostsfilePath, JSON.stringify([
        cluster.hosts[1],
        cluster.hosts[1]
    ]));

    var chan = cluster.client.makeSubChannel({
        serviceName: 'server',
        filePath: hostsfilePath,
        refreshInterval: 500,
        requestDefaults: {
            headers: {
                cn: 'client',
                as: 'raw'
            }
        }
    });

    chan.request({
        serviceName: 'server',
        hasNoParent: true
    }).send('echo', 'a', 'b', onResponse);

    function onResponse(err, res) {
        assert.ifError(err, 'request with peer should not fail');
        assert.equal(res.ok, true, 'response should be ok');

        assert.equal(chan.peers.keys().length, 1,
            'steve should only have one peer');

        assert.end();
    }
});

allocCluster.test('remove a peer and request', {
    numPeers: 2
}, function t(cluster, assert) {
    setup(cluster);

    cluster.logger.whitelist('info', 'ChannelWatcher: Removing old peer');
    cluster.logger.whitelist('info', 'draining peer');

    var hostsfilePath = path.join(os.tmpdir(), 'channel-watch-test.json');
    fs.writeFileSync(hostsfilePath, JSON.stringify([cluster.hosts[1]]));

    var chan = cluster.client.makeSubChannel({
        serviceName: 'server',
        filePath: hostsfilePath,
        refreshInterval: 500,
        requestDefaults: {
            headers: {
                cn: 'client',
                as: 'raw'
            }
        }
    });

    chan.request({
        serviceName: 'server',
        hasNoParent: true
    }).send('echo', 'a', 'b', onResponse);

    function onResponse(err, res, arg2, arg3) {
        assert.ifError(err, 'request with peer should not fail');
        assert.equal(res.ok, true, 'response should be ok');
        assert.equal(String(arg2), 'a', 'arg2 should be correct');
        assert.equal(String(arg3), 'b server by 1', 'arg3 should be correct');

        fs.writeFileSync(hostsfilePath, JSON.stringify([]));

        // wait for watch to reload the file
        setTimeout(function onDelay() {
            chan.request({
                serviceName: 'steve',
                hasNoParent: true
            }).send('echo', 'a', 'b', onResponse2);
        }, 700);
    }

    function onResponse2(err) {
        assert.ok(err, 'expect request error');
        assert.equal(err.type, 'tchannel.no-peer-available',
            'expected no peer available');

        assert.end();
    }
});

function sendRequests(channel, count, cb) {
    var responses = [];

    for (var i = 0; i < count; i++) {
        channel.request({
            serviceName: 'server',
            hasNoParent: true,
            timeout: 500,
            headers: {
                cn: 'client',
                as: 'raw'
            }
        }).send('echo', 'a', 'body', onResponse);
    }

    function onResponse(err, resp) {
        if (err) {
            if (cb !== null) {
                cb(err);
                cb = null;
            }
            return;
        }

        responses.push(resp);
        if (responses.length === count) {
            cb(null, responses);
        }
    }
}

function rollingSendRequsets(channel, count, cb) {
    var responses = [];

    var index = 0;

    setImmediate(fireOnce);

    function fireOnce() {
        channel.request({
            serviceName: 'server',
            hasNoParent: true,
            timeout: 500,
            headers: {
                cn: 'client',
                as: 'raw'
            }
        }).send('echo', 'a', 'body', onResponse);

        index++;

        if (index < count) {
            setImmediate(fireOnce);
        }
    }

    function onResponse(err, resp) {
        if (err) {
            if (cb !== null) {
                cb(err);
                cb = null;
            }
            return;
        }

        responses.push(resp);
        if (responses.length === count) {
            cb(null, responses);
        }
    }
}

function setup(cluster) {
    cluster.logger.whitelist(
        'info', 'ChannelWatcher: Loading peer list from file sync'
    );
    cluster.logger.whitelist(
        'info', 'ChannelWatcher: Loaded peers'
    );
    cluster.logger.whitelist(
        'info', 'ChannelWatcher: Loading peer list from file async'
    );

    cluster.client = cluster.channels[0];

    cluster.servers = cluster.channels.slice(1);
    cluster.serverHosts = [];

    for (var i = 0; i < cluster.servers.length; i++) {
        cluster.serverHosts.push(cluster.servers[i].hostPort);
        makeServer(cluster.servers[i], i);
    }
}

function makeServer(channel, index) {
    var chanNum = index + 1;

    var serverChan = channel.makeSubChannel({
        serviceName: 'server'
    });

    serverChan.register('echo', function echo(req, res, head, body) {
        res.headers.as = 'raw';
        res.sendOk(head, body + ' server by ' + chanNum);
    });
}
<|endoftext|>