def calcTeamStats(result_list: list, player_stats: dict, account_id: int) -> list: return_list = [] stat_types = player_stats[list(player_stats.keys())[0]].keys() for result in result_list: try: n_allies = collections.defaultdict(defaultvalueZero) allies_stats = collections.defaultdict(defaultvalueZero) bu.verbose('Allies') for ally in result['allies']: # Player itself is not in 'allies': see readReplayJSON() for stat in stat_types: if player_stats[ally][stat] != None: allies_stats[stat] += player_stats[ally][stat] n_allies[stat] += 1 bu.verbose( str(ally) + ':' + str(stat) + ':' + str(player_stats[ally][stat])) for stat in stat_types: if n_allies[stat] != None: if n_allies[stat] != 0: result['allies_' + str(stat)] = allies_stats[stat] / n_allies[stat] else: result['allies_' + str(stat)] = None n_enemies = collections.defaultdict(defaultvalueZero) enemies_stats = collections.defaultdict(defaultvalueZero) bu.verbose('Enemies') for enemy in result['enemies']: for stat in stat_types: if player_stats[enemy][stat] != None: enemies_stats[stat] += player_stats[enemy][stat] n_enemies[stat] += 1 bu.verbose( str(enemy) + ':' + str(stat) + ':' + str(player_stats[enemy][stat])) for stat in stat_types: if n_enemies[stat] != None: if n_enemies[stat] != 0: result[ 'enemies_' + str(stat)] = enemies_stats[stat] / n_enemies[stat] else: result['enemies_' + str(stat)] = None bu.verbose('result:' + str(result)) return_list.append(result) except KeyError as err: bu.error('Key :' + str(err) + ' not found') except Exception as err: bu.error(err) return return_list
async def replayWorker(queue: asyncio.Queue, workerID: int, account_id: int, priv = False): """Async Worker to process the replay queue""" global SKIPPED_N while True: item = await queue.get() filename = item[0] N = item[1] title = item[2] replay_json_fn = filename + '.json' msg_str = 'Replay[' + str(N) + ']: ' #bu.debug(msg_str + replay_json_fn) try: #if os.path.exists(replay_json_fn) and os.path.isfile(replay_json_fn): if os.path.isfile(replay_json_fn): async with aiofiles.open(replay_json_fn) as fp: replay_json = json.loads(await fp.read()) #if replay_json['status'] == 'ok': if wi.chkJSONreplay(replay_json): bu.verbose(msg_str + title + ' has already been posted. Skipping.' ) SKIPPED_N += 1 queue.task_done() continue except asyncio.CancelledError as err: raise err except Exception as err: bu.error(msg_str + 'Unexpected error: ' + str(type(err)) + ' : '+ str(err)) try: #bu.debug('Opening file [' + str(N) +']: ' + filename) async with aiofiles.open(filename,'rb') as fp: filename = os.path.basename(filename) bu.debug(msg_str + 'File: ' + filename) json_resp = await wi.postReplay(await fp.read(), filename, account_id, title, priv, N) if json_resp != None: if (await bu.saveJSON(replay_json_fn,json_resp)): bu.debug(msg_str + 'Replay saved OK: ' + filename ) else: bu.error(msg_str + 'Error saving replay: ' + filename) except Exception as err: bu.error(msg_str + 'Unexpected Exception: ' + str(type(err)) + ' : ' + str(err) ) bu.debug(msg_str + 'Marking task done') queue.task_done() return None
async def replayReader(queue: asyncio.Queue, readerID: int, args: argparse.Namespace): """Async Worker to process the replay queue""" #global SKIPPED_N account_id = args.accountID results = [] playerstanks = set() try: while True: item = await queue.get() filename = item[0] replayID = item[1] try: replay_json = await bu.readJSON(filename, wi.chkJSONreplay) if replay_json == None: bu.verbose('Replay[' + str(replayID) + ']: ' + filename + ' is invalid. Skipping.') #SKIPPED_N += 1 queue.task_done() continue ## Read the replay JSON bu.debug('[' + str(readerID) + '] reading replay: ' + filename) result = await readReplayJSON(replay_json, args) if result == None: bu.error('[' + str(readerID) + '] Error reading replay: ' + filename) queue.task_done() continue if (account_id != None): playerstanks.update(set(result['allies'])) playerstanks.update(set(result['enemies'])) results.append(result) bu.debug('Marking task ' + str(replayID) + ' done') except Exception as err: bu.error(str(err)) queue.task_done() except asyncio.CancelledError: return results, playerstanks return None
async def main(argv): global wg, wi TASK_N = 7 parser = argparse.ArgumentParser(description='ANalyze Blitz replay JSONs from WoTinspector.com') parser.add_argument('--output', default='plain', choices=['json', 'plain', 'db'], help='Select output mode: JSON, plain text or database') parser.add_argument('-id', dest='accountID', type=int, default=None, help='WG account_id to analyze') parser.add_argument('-a', '--account', dest='account', type=str, default=None, help='WG account nameto analyze. Format: ACCOUNT_NAME@SERVER') parser.add_argument('-u', '--url', dest= 'url', action='store_true', default=False, help='Print replay URLs') parser.add_argument('--tankfile', type=str, default='tanks.json', help='JSON file to read Tankopedia from. Default is "tanks.json"') parser.add_argument('--mapfile', type=str, default='maps.json', help='JSON file to read Blitz map names from. Default is "maps.json"') parser.add_argument('-o','--outfile', type=str, default='-', metavar="OUTPUT", help='File to write results. Default STDOUT') parser.add_argument('-d', '--debug', action='store_true', default=False, help='Debug mode') parser.add_argument('-v', '--verbose', action='store_true', default=False, help='Verbose mode') parser.add_argument('files', metavar='FILE1 [FILE2 ...]', type=str, nargs='+', help='Files to read. Use \'-\' for STDIN"') args = parser.parse_args() bu.setVerbose(args.verbose) bu.setDebug(args.debug) wg = WG(WG_appID, args.tankfile, args.mapfile) wi = WoTinspector() if args.account != None: args.accountID = await wg.getAccountID(args.account) bu.debug('WG account_id: ' + str(args.accountID)) try: replayQ = asyncio.Queue() reader_tasks = [] # Make replay Queue scanner_task = asyncio.create_task(mkReplayQ(replayQ, args.files)) # Start tasks to process the Queue for i in range(TASK_N): reader_tasks.append(asyncio.create_task(replayReader(replayQ, i, args))) bu.debug('Task ' + str(i) + ' started') bu.debug('Waiting for the replay scanner to finish') await asyncio.wait([scanner_task]) bu.debug('Scanner finished. Waiting for replay readers to finish the queue') await replayQ.join() bu.debug('Replays read. Cancelling Readers and analyzing results') for task in reader_tasks: task.cancel() results = [] playerstanks = set() for res in await asyncio.gather(*reader_tasks): results.extend(res[0]) playerstanks.update(res[1]) player_stats = await processTankStats(playerstanks, TASK_N) bu.verbose('') results = calcTeamStats(results, player_stats, args.accountID) processStats(results, args) finally: ## Need to close the aiohttp.session since Python destructors do not support async methods... await wg.close() await wi.close() return None
async def main(argv): global wg, wi, WG_APP_ID # set the directory for the script current_dir = os.getcwd() os.chdir(os.path.dirname(sys.argv[0])) ## Default options: OPT_DB = False OPT_EXTENDED = False OPT_HIST = False OPT_STAT_FUNC = 'player' OPT_WORKERS_N = 10 WG_ACCOUNT = None # format: nick@server, where server is either 'eu', 'ru', 'na', 'asia' or 'china'. # China is not supported since WG API stats are not available there WG_ID = None # WG account_id in integer format. # WG_ACCOUNT will be used to retrieve the account_id, but it can be set directly too # WG_APP_ID = WG_APP_ID WG_RATE_LIMIT = 10 ## WG standard. Do not edit unless you have your ## own server app ID, it will REDUCE the performance ## VERY unlikely you have a DB set up DB_SERVER = 'localhost' DB_PORT = 27017 DB_SSL = False DB_CERT_REQ = ssl.CERT_NONE DB_AUTH = 'admin' DB_NAME = 'BlitzStats' DB_USER = '******' DB_PASSWD = 'PASSWORD' DB_CERT = None DB_CA = None try: ## Read config if os.path.isfile(FILE_CONFIG): config = configparser.ConfigParser() config.read(FILE_CONFIG) try: if 'OPTIONS' in config.sections(): configOptions = config['OPTIONS'] # WG account id of the uploader: # # Find it here: https://developers.wargaming.net/reference/all/wotb/account/list/ OPT_DB = configOptions.getboolean('opt_DB', OPT_DB) OPT_EXTENDED = configOptions.getboolean('opt_analyzer_extended', OPT_EXTENDED) OPT_HIST = configOptions.getboolean('opt_analyzer_hist', OPT_HIST) OPT_STAT_FUNC = configOptions.get('opt_analyzer_stat_func', fallback=OPT_STAT_FUNC) OPT_WORKERS_N = configOptions.getint('opt_analyzer_workers', OPT_WORKERS_N) except (KeyError, configparser.NoSectionError) as err: bu.error(exception=err) try: if 'ANALYZER' in config.sections(): configAnalyzer = config['ANALYZER'] histogram_fields_str = configAnalyzer.get('histogram_buckets', None) if histogram_fields_str != None: set_histogram_buckets(json.loads(histogram_fields_str)) except (KeyError, configparser.NoSectionError) as err: bu.error(exception=err) try: if 'WG' in config.sections(): configWG = config['WG'] WG_ID = configWG.getint('wg_id', WG_ID) WG_ACCOUNT = configWG.get('wg_account', WG_ACCOUNT) WG_APP_ID = configWG.get('wg_app_id', WG_APP_ID) WG_RATE_LIMIT = configWG.getint('wg_rate_limit', WG_RATE_LIMIT) except (KeyError, configparser.NoSectionError) as err: bu.error(exception=err) try: if 'DATABASE' in config.sections(): configDB = config['DATABASE'] DB_SERVER = configDB.get('db_server', DB_SERVER) DB_PORT = configDB.getint('db_port', DB_PORT) DB_SSL = configDB.getboolean('db_ssl', DB_SSL) DB_CERT_REQ = configDB.getint('db_ssl_req', DB_CERT_REQ) DB_AUTH = configDB.get('db_auth', DB_AUTH) DB_NAME = configDB.get('db_name', DB_NAME) DB_USER = configDB.get('db_user', DB_USER) DB_PASSWD = configDB.get('db_password', DB_PASSWD) DB_CERT = configDB.get('db_ssl_cert_file', DB_CERT) DB_CA = configDB.get('db_ssl_ca_file', DB_CA) except (KeyError, configparser.NoSectionError) as err: bu.error(exception=err) parser = ErrorCatchingArgumentParser(description='Analyze Blitz replay JSON files from WoTinspector.com. Use \'upload_wotb_replays.py\' to upload the replay files first.') parser.add_argument('--output', default='plain', choices=['plain', 'db'], help='Select output mode: plain text or database') parser.add_argument('-id', dest='account_id', type=int, default=WG_ID, help='WG account_id to analyze') parser.add_argument('-a', '--account', type=str, default=WG_ACCOUNT, help='WG account nameto analyze. Format: ACCOUNT_NAME@SERVER') parser.add_argument('-x', '--extended', action='store_true', default=OPT_EXTENDED, help='Print Extended stats') parser.add_argument('-X', '--extra_categories', choices=BattleRecordCategory.get_extra_categories(), default=None, nargs='*', help='Print Extended categories') parser.add_argument('--hist', action='store_true', default=OPT_HIST, help='Print player histograms (WR/battles)') parser.add_argument('--stat_func', default=OPT_STAT_FUNC, choices=STAT_FUNC.keys(), help='Select how to calculate for ally/enemy performance: tank-tier stats, global player stats') parser.add_argument('-u', '--url', action='store_true', default=False, help='Print replay URLs') parser.add_argument('--tankfile', type=str, default='tanks.json', help='JSON file to read Tankopedia from. Default is "tanks.json"') parser.add_argument('--mapfile', type=str, default='maps.json', help='JSON file to read Blitz map names from. Default is "maps.json"') parser.add_argument('-o','--outfile', type=str, default='-', metavar="OUTPUT", help='File to write results. Default STDOUT') parser.add_argument('--db', action='store_true', default=OPT_DB, help='Use DB - You are unlikely to have it') parser.add_argument('--filters', type=str, default=None, help='Filters for DB based analyses. MongoDB find() filter JSON format.') parser.add_argument('-d', '--debug', action='store_true', default=False, help='Debug mode') parser.add_argument('-v', '--verbose', action='store_true', default=False, help='Verbose mode') parser.add_argument('-s', '--silent', action='store_true', default=False, help='Silent mode') parser.add_argument('files', metavar='FILE1 [FILE2 ...]', type=str, nargs='+', help='Files/dirs to read. Use \'-\' for STDIN, "db:" for database') try: args = parser.parse_args() except Exception as err: raise bu.set_log_level(args.silent, args.verbose, args.debug) bu.set_progress_step(250) # Set the frequency of the progress dots. wg = WG(WG_APP_ID, args.tankfile, args.mapfile, stats_cache=True, rate_limit=WG_RATE_LIMIT) wi = WoTinspector(rate_limit=10) if args.account != None: args.account_id = await wg.get_account_id(args.account) bu.debug('WG account_id: ' + str(args.account_id)) BattleRecord.set_fields(args.extended) #### Connect to MongoDB (TBD) bu.debug('DB_SERVER: ' + DB_SERVER) bu.debug('DB_PORT: ' + str(DB_PORT)) bu.debug('DB_SSL: ' + "True" if DB_SSL else "False") bu.debug('DB_AUTH: ' + DB_AUTH) bu.debug('DB_NAME: ' + DB_NAME) client = None db = None if args.db: try: client = motor.motor_asyncio.AsyncIOMotorClient(DB_SERVER,DB_PORT, authSource=DB_AUTH, username=DB_USER, password=DB_PASSWD, ssl=DB_SSL, ssl_cert_reqs=DB_CERT_REQ, ssl_certfile=DB_CERT, tlsCAFile=DB_CA) db = client[DB_NAME] args.account_id = None bu.debug('Database connection initiated') except Exception as err: bu.error("Could no initiate DB connection: Disabling DB", err) args.db = False pass else: bu.debug('No DB in use') # rebase file arguments due to moving the working directory to the script location args.files = bu.rebase_file_args(current_dir, args.files) try: replayQ = asyncio.Queue(maxsize=1000) reader_tasks = [] # Make replay Queue scanner_task = asyncio.create_task(mk_replayQ(replayQ, args, db)) bu.debug('Replay scanner started') # Start tasks to process the Queue for i in range(OPT_WORKERS_N): reader_tasks.append(asyncio.create_task(replay_reader(replayQ, i, args))) bu.debug('ReplayReader ' + str(i) + ' started') bu.debug('Waiting for the replay scanner to finish') await asyncio.wait([scanner_task]) # bu.debug('Scanner finished. Waiting for replay readers to finish the queue') await replayQ.join() await asyncio.sleep(0.1) bu.debug('Replays read. Cancelling Readers and analyzing results') for task in reader_tasks: task.cancel() await asyncio.sleep(0.1) results = [] players = set() for res in await asyncio.gather(*reader_tasks): results.extend(res[0]) players.update(res[1]) if len(players) == 0: raise Exception("No players found to fetch stats for. No replays found?") (player_stats, stat_id_map) = await process_player_stats(players, OPT_WORKERS_N, args, db) bu.verbose('') bu.debug('Number of player stats: ' + str(len(player_stats))) teamresults = calc_team_stats(results, player_stats, stat_id_map, args) process_battle_results(teamresults, args) if args.hist: print('\nPlayer Histograms______', end='', flush=True) process_player_dist(results, player_stats, stat_id_map) bu.debug('Finished. Cleaning up..................') except Exception as err: bu.error(exception=err) except UserWarning as err: bu.verbose(str(err)) pass except Exception as err: bu.error(exception=err) finally: ## Need to close the aiohttp.session since Python destructors do not support async methods... if wg != None: await wg.close() if wi != None: await wi.close() return None
async def mk_replayQ(queue : asyncio.Queue, args : argparse.Namespace, db : motor.motor_asyncio.AsyncIOMotorDatabase = None): """Create queue of replays to post""" p_replayfile = re.compile('.*\\.wotbreplay\\.json$') files = args.files Nreplays = 0 if files[0] == 'db:': if db == None: bu.error('No database connection opened') sys.exit(1) try: dbc = db[DB_C_REPLAYS] cursor = None if args.filters != None: bu.debug(str(args.filters)) filters = json.loads(args.filters) bu.debug(json.dumps(filters, indent=2)) cursor = dbc.find(filters) else: # select all cursor = dbc.find({}) bu.debug('Reading replays...') async for replay_json in cursor: _id = replay_json['_id'] del(replay_json['_id']) await queue.put(await mk_readerQ_item(replay_json, 'DB: _id = ' + _id)) Nreplays += 1 bu.debug('All the matching replays have been read from the DB') except Exception as err: bu.error(exception=err) elif files[0] == '-': bu.debug('reading replay file list from STDIN') stdin, _ = await aioconsole.get_standard_streams() while True: try: line = (await stdin.readline()).decode('utf-8').rstrip() if not line: break if (p_replayfile.match(line) != None): replay_json = await bu.open_JSON(line, wi.chk_JSON_replay) await queue.put(await mk_readerQ_item(replay_json, line)) except Exception as err: bu.error(exception=err) else: for fn in files: try: # bu.debug('Filename: ' + fn) if fn.endswith('"'): fn = fn[:-1] if os.path.isfile(fn) and (p_replayfile.match(fn) != None): replay_json = await bu.open_JSON(fn, wi.chk_JSON_replay) await queue.put(await mk_readerQ_item(replay_json, fn)) bu.debug('File added to queue: ' + fn) Nreplays += 1 elif os.path.isdir(fn): with os.scandir(fn) as dirEntry: for entry in dirEntry: if entry.is_file() and (p_replayfile.match(entry.name) != None): bu.debug(entry.name) replay_json = await bu.open_JSON(entry.path, wi.chk_JSON_replay) await queue.put(await mk_readerQ_item(replay_json, entry.name)) bu.debug('File added to queue: ' + entry.path) Nreplays += 1 except Exception as err: bu.error(exception=err) bu.verbose('Finished scanning replays: ' + str(Nreplays) + ' replays to process') return Nreplays
async def main(argv): global wg, wi parser = argparse.ArgumentParser(description='Post replays(s) to WoTinspector.com and retrieve battle data') parser.add_argument('--output', default='single', choices=['file', 'files', 'db'] , help='Select output mode: single/multiple files or database') parser.add_argument('-id', dest='accountID', type=int, default=WG_ID, help='WG account_id') parser.add_argument('-a', '--account', dest='account', type=str, default=None, help='Uploader\'s WG account name. Format: ACCOUNT_NAME@SERVER') parser.add_argument('-t','--title', type=str, default=None, help='Title for replays. Use NN for continous numbering. Default is filename-based numbering') parser.add_argument('-p', '--private', dest="private", action='store_true', default=False, help='Set replays private on WoTinspector.com') parser.add_argument('--tasks', dest='N_tasks', type=int, default=10, help='Number of worker threads') parser.add_argument('--tankopedia', type=str, default='tanks.json', help='JSON file to read Tankopedia from. Default: "tanks.json"') parser.add_argument('--mapfile', type=str, default='maps.json', help='JSON file to read Blitz map names from. Default: "maps.json"') parser.add_argument('-d', '--debug', action='store_true', default=False, help='Debug mode') parser.add_argument('-v', '--verbose', action='store_true', default=True, help='Verbose mode') parser.add_argument('-s', '--silent', action='store_true', default=False, help='Silent mode') parser.add_argument('files', metavar='FILE1 [FILE2 ...]', type=str, nargs='+', help='Files to read. Use \'-\' for STDIN"') args = parser.parse_args() bu.setVerbose(args.verbose) bu.setDebug(args.debug) if args.silent: bu.setVerbose(False) wg = WG(WG_appID, args.tankopedia, args.mapfile) wi = WoTinspector() if args.account != None: args.accountID = await wg.getAccountID(args.account) bu.debug('WG account_id: ' + str(args.accountID)) if args.accountID == None: args.accountID = 0 try: queue = asyncio.Queue() tasks = [] # Make replay Queue tasks.append(asyncio.create_task(mkReplayQ(queue, args.files, args.title))) # Start tasks to process the Queue for i in range(args.N_tasks): tasks.append(asyncio.create_task(replayWorker(queue, i, args.accountID, args.private))) bu.debug('Task ' + str(i) + ' started') bu.debug('Waiting for the replay scanner to finish') await asyncio.wait([tasks[0]]) bu.debug('Scanner finished. Waiting for workers to finish queue') await queue.join() bu.debug('Cancelling workers') for task in tasks: task.cancel() bu.debug('Waiting for workers to cancel') await asyncio.gather(*tasks, return_exceptions=True) bu.verbose(str(REPLAY_N) + ' replays: ' + str(REPLAY_N - SKIPPED_N) + ' uploaded, ' + str(SKIPPED_N) + ' skipped') except KeyboardInterrupt: print('Ctrl-c pressed ...') sys.exit(1) finally: ## Need to close the aiohttp.session since Python destructors do not support async methods... await wg.session.close() await wi.close() return None
async def main(argv): global wg, wi # set the directory for the script os.chdir(os.path.dirname(sys.argv[0])) ## Read config config = configparser.ConfigParser() config.read(FILE_CONFIG) configOptions = config['OPTIONS'] OPT_WORKERS_N = configOptions.getint('opt_uploader_workers', 5) configWG = config['WG'] # WG account id of the uploader: # # Find it here: https://developers.wargaming.net/reference/all/wotb/account/list/ WG_ID = configWG.getint('wg_id', None) ## WG API Rules limit 10 request / sec. Higher rate of requests will return errors ==> extra delay WG_RATE_LIMIT = configWG.getint('wg_rate_limit', 10) parser = argparse.ArgumentParser( description= 'Post replays(s) to WoTinspector.com and retrieve battle data') parser.add_argument('-id', dest='accountID', type=int, default=WG_ID, help='WG account_id') parser.add_argument( '-a', '--account', dest='account', type=str, default=None, help='Uploader\'s WG account name. Format: ACCOUNT_NAME@SERVER') parser.add_argument( '-t', '--title', type=str, default=None, help= 'Title for replays. Use NN for continous numbering. Default is filename-based numbering' ) parser.add_argument('-p', '--private', dest="private", action='store_true', default=False, help='Set replays private on WoTinspector.com') parser.add_argument( '--tankopedia', type=str, default='tanks.json', help='JSON file to read Tankopedia from. Default: "tanks.json"') parser.add_argument( '--mapfile', type=str, default='maps.json', help='JSON file to read Blitz map names from. Default: "maps.json"') parser.add_argument('-d', '--debug', action='store_true', default=False, help='Debug mode') parser.add_argument('-v', '--verbose', action='store_true', default=False, help='Verbose mode') parser.add_argument('-s', '--silent', action='store_true', default=False, help='Silent mode') parser.add_argument('files', metavar='FILE1 [FILE2 ...]', type=str, nargs='+', help='Files to read. Use \'-\' for STDIN"') args = parser.parse_args() bu.set_verbose(args.verbose) bu.set_log_level(args.silent, args.verbose, args.debug) wg = WG(WG_appID, args.tankopedia, args.mapfile) wi = WoTinspector(rate_limit=WG_RATE_LIMIT) if args.account != None: args.accountID = await wg.get_account_id(args.account) bu.debug('WG account_id: ' + str(args.accountID)) if args.accountID == None: args.accountID = 0 try: queue = asyncio.Queue() tasks = [] # Make replay Queue tasks.append( asyncio.create_task(mkReplayQ(queue, args.files, args.title))) # Start tasks to process the Queue for i in range(OPT_WORKERS_N): tasks.append( asyncio.create_task( replayWorker(queue, i, args.accountID, args.private))) bu.debug('Task ' + str(i) + ' started') bu.debug('Waiting for the replay scanner to finish') await asyncio.wait([tasks[0]]) bu.debug('Scanner finished. Waiting for workers to finish queue') await queue.join() bu.debug('Cancelling workers') for task in tasks: task.cancel() bu.debug('Waiting for workers to cancel') await asyncio.gather(*tasks, return_exceptions=True) bu.verbose( str(REPLAY_N) + ' replays: ' + str(REPLAY_N - SKIPPED_N - ERROR_N) + ' uploaded, ' + str(SKIPPED_N) + ' skipped, ' + str(ERROR_N) + ' errors') except KeyboardInterrupt: print('Ctrl-c pressed ...') sys.exit(1) finally: ## Need to close the aiohttp.session since Python destructors do not support async methods... await wg.session.close() await wi.close() return None