Пример #1
0
def update(config_path, verbose):
    '''
    Run updates. Pass me path to config file which contains settings for redis
    as well as which soldat servers to process data for.
  '''

    with acquire_lock():

        start = time.time()
        # Parse our config yaml file
        config = Config(config_path)

        r = redis.Redis(**config.redis_connect)

        for server in config.servers:
            print(
                'Updating stats for {server.url_slug} ({server.title}) ({server.log_source})'
                .format(server=server))

            # Redis key name manager
            keys = Keys(config, server)

            # Limit our data to however much retention
            retention = Retention(r, keys, config, server)

            # Parse each of our soldat DIRs
            for soldat_dir in server.dirs:

                # Support getting logs via local files or ssh or ftp
                if server.log_source == 'local':
                    filemanager = LocalFileManager(r, keys, soldat_dir,
                                                   retention)
                elif server.log_source == 'ssh':
                    filemanager = SshFileManager(r, keys, soldat_dir,
                                                 retention,
                                                 server.connection_options)
                elif server.log_source == 'ftp':
                    filemanager = FtpFileManager(r, keys, soldat_dir,
                                                 retention,
                                                 server.connection_options)

                # Console logs
                try:
                    update_events(r, keys, retention, filemanager, server,
                                  verbose)
                except Exception:
                    logging.exception('Failed updating stats for %s (%s) (%s)',
                                      server.url_slug, server.title,
                                      server.log_source)

            # Trim old events
            retention.run_retention()
            print('Updating took {0} seconds'.format(
                round(time.time() - start, 2)))
Пример #2
0
def test_keys():
    k = Keys(FakeConfig(), FakeServer())

    assert k.top_players == 'pystats:server1:top_players'
    assert k.player_search == 'pystats:server1:player_search'
    assert k.kill_log == 'pystats:server1:kill_log'
    assert k.top_weapons == 'pystats:server1:top_weapons'
    assert k.top_countries == 'pystats:server1:top_countries'
    assert k.top_maps == 'pystats:server1:top_maps'
    assert k.kills_per_day(
        '2016-10-30') == 'pystats:server1:kills_per_day:2016-10-30'
    assert k.player_hash('jrgp') == 'pystats:server1:player:jrgp'
    assert k.player_top_enemies(
        'jrgp') == 'pystats:server1:player_top_enemies:jrgp'
    assert k.player_top_victims(
        'jrgp') == 'pystats:server1:player_top_victims:jrgp'
    assert k.weapon_top_killers(
        'HK MP5') == 'pystats:server1:weapon_top_killers:HK MP5'
    assert k.map_hash('Adore') == 'pystats:server1:map:Adore'
    assert k.round_hash(1) == 'pystats:server1:round_data:1'
    assert k.player_id_to_names(1) == 'pystats:server1:player_id_to_names:1'
Пример #3
0
 def __init__(self, config, server):
   self.r = redis.Redis(connection_pool=config.redis_connection_pool)
   self.keys = Keys(config, server)
   self.server = server
Пример #4
0
class Results():
  def __init__(self, config, server):
    self.r = redis.Redis(connection_pool=config.redis_connection_pool)
    self.keys = Keys(config, server)
    self.server = server

  def get_num_kills(self):
    try:
      return int(self.r.llen(self.keys.kill_log))
    except ValueError:
      return 0

  def get_num_players(self):
    try:
      return int(self.r.zcard(self.keys.top_players))
    except ValueError:
      return 0

  def get_top_killers(self, startat=0, incr=20):
    results = self.r.zrevrange(self.keys.top_players, startat, startat + incr, withscores=True)
    for name, kills in results:
      more = {}
      for key in ['deaths', 'lastseen', 'firstseen', 'lastcountry']:
        more[key] = self.r.hget(self.keys.player_hash(name), key)
      yield Player(name=name,
                   kills=kills,
                   **more
                   )

  def get_player(self, _player):
    info = self.r.hgetall(self.keys.player_hash(_player))
    if not info:
      return None
    return Player(name=_player, **info)

  def get_player_fields(self, _player, fields=[]):
    info = {}
    for key in fields:
      info[key] = self.r.hget(self.keys.player_hash(_player), key)
    return Player(name=_player, **info)

  def get_player_top_enemies(self, _player, startat=0, incr=20):
    results = self.r.zrevrange(self.keys.player_top_enemies(_player), 0, startat + incr, withscores=True)
    for name, kills in results:
      more = {}
      for key in ['lastcountry']:
        more[key] = self.r.hget(self.keys.player_hash(name), key)
      yield Player(name=name,
                   kills=kills,
                   **more
                   )

  def get_player_top_victims(self, _player, startat=0, incr=20):
    results = self.r.zrevrange(self.keys.player_top_victims(_player), 0, startat + incr, withscores=True)
    for name, kills in results:
      more = {}
      for key in ['lastcountry']:
        more[key] = self.r.hget(self.keys.player_hash(name), key)
      yield Player(name=name,
                   kills=kills,
                   **more
                   )

  def get_last_kills(self, startat=0, incr=20):
    for kill in self.r.lrange(self.keys.kill_log, startat, startat + incr):
      yield pickle.loads(kill)

  def get_top_weapons(self):
    results = self.r.zrevrange(self.keys.top_weapons, 0, 20, withscores=True)
    return map(lambda x: (x[0], int(x[1])), results)

  def get_kills_for_date_range(self, startdate=None, previous_days=7):
    if not isinstance(startdate, datetime):
      startdate = datetime.utcnow()

    stats = OrderedDict()

    for x in range(previous_days):
      current_date = startdate - timedelta(days=x)
      key = self.keys.kills_per_day(str(current_date.date()))
      try:
        count = int(self.r.get(key))
      except (TypeError, ValueError):
        count = 0
      stats[current_date] = count

    return stats

  def get_top_countries(self, limit=10):
    return self.r.zrevrange(self.keys.top_countries, 0, limit, withscores=True)

  def get_weapon_stats(self, name):
    kills = self.r.zscore(self.keys.top_weapons, name)
    if kills is None:
      return False

  def player_search(self, name):
    resultset = self.r.zscan(
        self.keys.top_players,
        0,
        '*{name}*'.format(name=name),
        self.get_num_players())
    return resultset[1][:20]
Пример #5
0
 def __init__(self, config, server):
   self.r = redis.Redis(**config.redis_connect)
   self.keys = Keys(config, server)
   self.server = server
Пример #6
0
 def __init__(self, config, server):
   self.r = redis.Redis(connection_pool=config.redis_connection_pool)
   self.keys = Keys(config, server)
   self.server = server
Пример #7
0
class Results():
  ''' Abstraction used to scrape redis to provide details to web app '''
  def __init__(self, config, server):
    self.r = redis.Redis(connection_pool=config.redis_connection_pool)
    self.keys = Keys(config, server)
    self.server = server

  def get_num_kills(self):
    try:
      return self.r.zcard(self.keys.kill_log)
    except ValueError:
      return 0

  def get_num_players(self):
    ''' number of players with kills we have '''
    try:
      return int(self.r.zcard(self.keys.top_players))
    except ValueError:
      return 0

  def get_num_maps(self):
    ''' number of maps we have '''
    try:
      return int(self.r.zcard(self.keys.top_maps))
    except ValueError:
      return 0

  def get_num_rounds(self):
    ''' number of rounds we have '''
    try:
      return self.r.zcard(self.keys.round_log)
    except ValueError:
      return 0

  def get_top_killers(self, startat=0, incr=20):
    ''' list of Player objects sorted by those with the most kills desc '''
    results = self.r.zrevrange(self.keys.top_players, startat, startat + incr, withscores=True)
    for player_id, kills in results:
      more = self.hmget_with_keys(self.keys.player_hash(player_id), ['deaths', 'lastseen', 'firstseen', 'lastcountry', 'scores:Alpha', 'scores:Bravo'])
      yield Player(name=self.get_name_from_id(player_id),
                   kills=kills,
                   **more
                   )

  def get_top_maps(self, startat=0, incr=20):
    ''' list of Map objects sorted by those with the most plays desc '''
    results = self.r.zrevrange(self.keys.top_maps, startat, startat + incr, withscores=True)
    for name, plays in results:
      more = self.hmget_with_keys(self.keys.map_hash(name), ['scores:Alpha', 'scores:Bravo', 'wins:bravo', 'wins:alpha', 'kills', 'flags'])
      yield Map(name=name,
                plays=plays,
                **more
                )

  def get_player(self, _player):
    ''' given a player id, get a Player object '''
    _player_id = self.get_id_from_name(_player)
    if not _player_id:
      return None
    info = self.r.hgetall(self.keys.player_hash(_player_id))
    if not info:
      return None
    info['names'] = self.get_all_names_from_id(_player_id)
    return Player(name=self.get_name_from_id(_player_id), **info)

  def get_round(self, _round):
    ''' given a round id, get a Round object '''
    info = self.r.hgetall(self.keys.round_hash(_round))
    if not info:
      return None
    return Round(round_id=_round, **info).resolve_players(self)

  def get_map(self, _map, get_svg=False):
    ''' given a map name, get a Map object '''
    # Manually get list of all keys we have for this map, so
    # if we don't want to get the gigantic svg xml blob, we
    # can selectively remove it
    keys = self.r.hkeys(self.keys.map_hash(_map))

    # No keys returned? Map doesn't exist
    if not keys:
      return None

    keys = set(keys)
    has_svg = 'svg_image' in keys

    if not get_svg:
      keys.discard('svg_image')

    keys = list(keys)

    info = self.hmget_with_keys(self.keys.map_hash(_map), keys)

    if not info:
      return None

    # Make sure the svg_exists property on the map model
    # will continue to work if we do in fact have the svg
    # but are not retrieving it, by setting its key to None
    if not get_svg and has_svg:
      info['svg_image'] = None

    info['plays'] = self.r.zscore(self.keys.top_maps, _map) or 0
    return Map(name=_map, **info)

  def get_player_fields_by_name(self, player_name, fields=[]):
    ''' given a player name and some keys, get back a populated Player object '''
    player_id = self.get_id_from_name(player_name)
    if not player_id:
      return
    return self.get_player_fields(player_id, fields)

  def get_player_fields(self, _player_id, fields=[]):
    ''' given a player id and some keys, get back a populated Player object '''
    info = {}
    for key in fields:
      info[key] = self.r.hget(self.keys.player_hash(_player_id), key)
    return Player(name=self.get_name_from_id(_player_id), **info)

  def get_player_top_enemies(self, _player, startat=0, incr=20):
    ''' given a player name, get list of Player objects for that player, sorted by number of times they killed us desc '''
    _player_id = self.get_id_from_name(_player)
    if not _player_id:
      return
    results = self.r.zrevrange(self.keys.player_top_enemies(_player_id), 0, startat + incr, withscores=True)
    for enemy_id, their_kills in results:
      more = self.hmget_with_keys(self.keys.player_hash(enemy_id), ['lastcountry'])
      my_deaths = float(self.r.zscore(self.keys.player_top_enemies(enemy_id), _player_id) or 0)
      more['kd'] = '%.2f' % (their_kills / my_deaths if my_deaths > 0 else 0)
      yield Player(name=self.get_name_from_id(enemy_id),
                   kills=their_kills,
                   **more
                   )

  def get_player_top_victims(self, _player, startat=0, incr=20):
    ''' given a player name, get list of Player objects for that player, sorted by number of times we killed them us desc '''
    _player_id = self.get_id_from_name(_player)
    if not _player_id:
      return
    results = self.r.zrevrange(self.keys.player_top_victims(_player_id), 0, startat + incr, withscores=True)
    for victim_id, my_kills in results:
      more = self.hmget_with_keys(self.keys.player_hash(victim_id), ['lastcountry'])
      their_deaths = float(self.r.zscore(self.keys.player_top_victims(victim_id), _player_id) or 0)
      more['kd'] = '%.2f' % (my_kills / their_deaths if their_deaths > 0 else 0)
      yield Player(name=self.get_name_from_id(victim_id),
                   kills=my_kills,
                   **more
                   )

  def get_last_kills(self, startat=0, incr=20):
    ''' given pagination, get list of most recent Kill objects '''
    kill_ids = self.r.zrevrange(self.keys.kill_log, startat, startat + incr)
    for kill_id in kill_ids:
      kill_data = self.r.hget(self.keys.kill_data, kill_id)
      if kill_data:
        yield Kill.from_redis(kill_data).resolve_player_ids(self.get_name_from_id)

  def get_last_rounds(self, startat=0, incr=20):
    ''' given pagination, get list of most recent Round objects '''
    round_ids = self.r.zrevrange(self.keys.round_log, startat, startat + incr)
    for round_id in round_ids:
      round_data = self.r.hgetall(self.keys.round_hash(round_id))
      if round_data:
        round_data['round_id'] = round_id
        yield Round(**round_data).resolve_players(self)

  def get_top_weapons(self):
    ''' get list of tuples of weapon to kills '''
    results = self.r.zrevrange(self.keys.top_weapons, 0, 20, withscores=True)
    return map(lambda x: (x[0], int(x[1])), results)

  def get_kills_for_date_range(self, startdate=None, previous_days=7):
    ''' get ordered dict of kills per day per given date range '''
    if not isinstance(startdate, datetime):
      startdate = datetime.utcnow()

    stats = OrderedDict()

    for x in range(previous_days):
      current_date = startdate - timedelta(days=x)
      key = self.keys.kills_per_day(str(current_date.date()))
      try:
        count = int(self.r.get(key))
      except (TypeError, ValueError):
        count = 0
      stats[current_date] = count

    return stats

  def get_top_countries(self, limit=10):
    ''' get list of tuples of countries and players from that country '''
    return self.r.zrevrange(self.keys.top_countries, 0, limit, withscores=True)

  def player_search(self, name):
    ''' search for players based on name fragment. return list of Player() objects sorted desc by last seen'''
    # escape glob characters so they act as expected as search terms
    name = name.replace('*', '\*').replace('?', '\?')

    player_ids = set()

    cursor = 0
    tries = 0

    # *maybe* these values are too high under some circumstances?
    max_tries = 100
    max_names = 100

    while tries < max_tries and len(player_ids) < max_names:
        res = self.r.hscan(self.keys.player_search, cursor, '*{name}*'.format(name=name.lower()), max_names)
        cursor = res[0]
        for name in res[1].values():
            player_id = self.get_id_from_name(name)
            if player_id:
                player_ids.add(player_id)
        if cursor == 0:
            break
        tries += 1

    return sorted((self.get_player_fields(player_id, ('lastcountry', 'lastseen', 'firstseen', 'kills')) for player_id in player_ids),
                  key=operator.attrgetter('lastseen'),
                  reverse=True)

  def get_name_from_id(self, player_id):
    ''' get most recent name this id is tied to '''
    names = self.get_all_names_from_id(player_id)
    if names:
      return names[0][0]
    else:
      return None

  def get_all_names_from_id(self, player_id):
    ''' get all names this ID is tied to, sorted by most recent use descending '''
    names_with_scores = self.r.zrevrange(self.keys.player_id_to_names(player_id), 0, -1, withscores=True)
    filtered = remove_redundant_player_names([r[0] for r in names_with_scores])
    names_with_scores_dict = dict(names_with_scores)
    return [(k, names_with_scores_dict[k]) for k in filtered]

  def get_id_from_name(self, name):
    ''' get latest id this name is tied to '''
    return self.r.hget(self.keys.name_to_id, name)

  def hmget_with_keys(self, hash_name, keys):
    ''' like self.r.hgetall except specify the keys you get back '''
    keys = list(keys)

    data = self.r.hmget(hash_name, keys)
    if not data:
      return {}

    return dict(zip(keys, data))