public ValueTask ExecuteAsync(IConsole console) { Log.Info($"Starting {nameof(BalanceClans)}..."); Log.Debug("Retrieving data diagnostics..."); var dd = _provider.GetDataDiagnostic(); Log.Info($"Scheduled Players Per Day: {dd.ScheduledPlayersPerDay:N0}"); Log.Info($"Target Players Per Minute: {PlayersPerMinute:N0}"); // Minutes, in average, per day, that the system is off, or not retrieving data const int averageOutTimeMinutes = 30; // The average clan size, as retrieved in 2020-07-30 const int averageClanSize = 14; var maxPerDay = PlayersPerMinute * (60 * 24 - averageOutTimeMinutes); var minPerDay = maxPerDay - 2 * averageClanSize; Log.Info($"Min players per day: {minPerDay:N0}"); Log.Info($"Max players per day: {maxPerDay:N0}"); if (dd.ScheduledPlayersPerDay < minPerDay || dd.ScheduledPlayersPerDay > maxPerDay) { Log.Info($"Balancing between {minPerDay:N0} and {maxPerDay:N0}."); _recorder.BalanceClanSchedule(minPerDay, maxPerDay); } console.Output.WriteLine("Done!"); Log.Info($"Done {nameof(BalanceClans)}."); return(default);
private static int Main(string[] args) { try { ParseParams(args, out var ageHours, out var maxRunMinutes, out var hourToDeleteOldFiles, out var daysToKeepOnDelete, out var calculateMoe, out var calculateReference, out var playersPerMinute, out var utcShiftToCalculate, out var waitForRemote); var resultDirectoryXbox = ConfigurationManager.AppSettings["ResultDirectory"]; var resultDirectoryPs = ConfigurationManager.AppSettings["PsResultDirectory"]; Log.Info("------------------------------------------------------------------------------------"); Log.Info("CalculateClanStats iniciando..."); Log.InfoFormat( "ageHours: {0}; maxRunMinutes: {1}; resultDirectory: {2}; resultDirectoryPs: {3}; utcShiftToCalculate: {4}; waitForRemote: {5}", ageHours, maxRunMinutes, resultDirectoryXbox, resultDirectoryPs, utcShiftToCalculate, waitForRemote); var sw = Stopwatch.StartNew(); var connectionString = ConfigurationManager.ConnectionStrings["Main"].ConnectionString; var provider = new DbProvider(connectionString); var clans = provider.GetClanCalculateOrder(ageHours).ToArray(); Log.InfoFormat("{0} clãs devem ser calculados.", clans.Length); const int averageOutTimeMinutes = 30; const int averageClanSize = 25; var maxPerDay = playersPerMinute * 60 * 24 - averageOutTimeMinutes * playersPerMinute; var minPerDay = maxPerDay - averageClanSize; Log.Info($"playersPerMinute: {playersPerMinute}; maxPerDay: {maxPerDay}; minPerDay: {minPerDay}"); var recorder = new DbRecorder(connectionString); var putterXbox = new FtpPutter(ConfigurationManager.AppSettings["FtpFolder"], ConfigurationManager.AppSettings["FtpUser"], ConfigurationManager.AppSettings["FtpPassworld"]); var putterPs = new FtpPutter(ConfigurationManager.AppSettings["PsFtpFolder"], ConfigurationManager.AppSettings["PsFtpUser"], ConfigurationManager.AppSettings["PsFtpPassworld"]); var mailSender = new MailSender(ConfigurationManager.AppSettings["SmtpHost"], int.Parse(ConfigurationManager.AppSettings["SmtpPort"]), true, ConfigurationManager.AppSettings["SmtpUsername"], ConfigurationManager.AppSettings["SmtpPassword"]) { From = new MailAddress(ConfigurationManager.AppSettings["SmtpUsername"], "Calculate Service") }; var webCacheAge = TimeSpan.FromMinutes(1); var cacheDirectory = ConfigurationManager.AppSettings["CacheDirectory"]; var fetcher = new Fetcher(cacheDirectory) { WebCacheAge = webCacheAge, WebFetchInterval = TimeSpan.FromSeconds(1), ApplicationId = ConfigurationManager.AppSettings["WgApi"] }; // Obtém o status, por causa da data dos MoE, para disparar o cálculo assíncrono DateTime lastMoeXbox, lastMoePs, lastReferencesXbox, lastReferencesPs; SiteDiagnostic siteStatusXbox, siteStatusPs; try { siteStatusXbox = fetcher.GetSiteDiagnostic( ConfigurationManager.AppSettings["RemoteSiteStatusApi"], ConfigurationManager.AppSettings["ApiAdminKey"]); siteStatusPs = fetcher.GetSiteDiagnostic( ConfigurationManager.AppSettings["PsRemoteSiteStatusApi"], ConfigurationManager.AppSettings["ApiAdminKey"]); lastMoeXbox = siteStatusXbox.TanksMoELastDate; lastMoePs = siteStatusPs.TanksMoELastDate; lastReferencesXbox = siteStatusXbox.TankLeadersLastDate; lastReferencesPs = siteStatusPs.TankLeadersLastDate; } catch (Exception ex) { Log.Error("Error getting initial site diagnostics", ex); // Since the site id offline... siteStatusXbox = siteStatusPs = new SiteDiagnostic(null); GetLastDatesFromFiles(resultDirectoryXbox, out lastMoeXbox, out lastReferencesXbox); GetLastDatesFromFiles(resultDirectoryPs, out lastMoePs, out lastReferencesPs); } Log.Info($"lastMoeXbox: {lastMoeXbox:yyyy-MM-dd}"); Log.Info($"lastMoePs: {lastMoePs:yyyy-MM-dd}"); Log.Info($"lastReferencesXbox: {lastReferencesXbox:yyyy-MM-dd}"); Log.Info($"lastReferencesPs: {lastReferencesPs:yyyy-MM-dd}"); // Dispara as tarefas de calculo gerais var calculationTask = Task.Run(() => RunCalculations(calculateReference, calculateMoe, lastMoeXbox, lastMoePs, lastReferencesXbox, lastReferencesPs, provider, recorder, mailSender, resultDirectoryXbox, resultDirectoryPs, putterXbox, putterPs, fetcher, utcShiftToCalculate)); // Dispara a tarefa de apagar arquivos antigos dos servidores var deleteTask = Task.Run(() => { if (DateTime.UtcNow.Hour != hourToDeleteOldFiles || daysToKeepOnDelete < 28) { return; } // Apaga arquivos com a API administrativa var cleanXBox = Task.Run(() => { try { var cleaner = new Putter(Platform.XBOX, ConfigurationManager.AppSettings["ApiAdminKey"]); cleaner.CleanFiles(); } catch (Exception ex) { Log.Error("Erro cleaning XBOX", ex); } }); var cleanPs = Task.Run(() => { try { var cleaner = new Putter(Platform.PS, ConfigurationManager.AppSettings["ApiAdminKey"]); cleaner.CleanFiles(); } catch (Exception ex) { Log.Error("Erro cleaning XBOX", ex); } }); if (waitForRemote) { Task.WhenAll(cleanXBox, cleanPs); } }); // Calcula cada clã var doneCount = 0; var timedOut = false; Parallel.For(0, clans.Length, new ParallelOptions { MaxDegreeOfParallelism = 2 }, i => { if (sw.Elapsed.TotalMinutes > maxRunMinutes) { timedOut = true; return; } var clan = clans[i]; Log.InfoFormat("Processando clã {0} de {1}: {2}@{3}...", i + 1, clans.Length, clan.ClanTag, clan.Plataform); var csw = Stopwatch.StartNew(); var cc = CalculateClan(clan, provider, recorder); Log.InfoFormat("Calculado clã {0} de {1}: {2}@{3} em {4:N1}s...", i + 1, clans.Length, clan.ClanTag, clan.Plataform, csw.Elapsed.TotalSeconds); if (cc != null) { var fsw = Stopwatch.StartNew(); switch (cc.Plataform) { case Platform.XBOX: { var fileName = cc.ToFile(resultDirectoryXbox); Log.InfoFormat("Arquivo de resultado escrito em '{0}'", fileName); var putTask = Task.Run(() => { try { putterXbox.PutClan(fileName); } catch (Exception ex) { Log.Error($"Error putting XBOX clan file for {cc.ClanTag}", ex); } }); if (waitForRemote) { putTask.Wait(); } } break; case Platform.PS: { var fileName = cc.ToFile(resultDirectoryPs); Log.InfoFormat("Arquivo de resultado escrito em '{0}'", fileName); var putTask = Task.Run(() => { try { putterPs.PutClan(fileName); } catch (Exception ex) { Log.Error($"Error putting PS clan file for {cc.ClanTag}", ex); } }); if (waitForRemote) { putTask.Wait(); } } break; case Platform.Virtual: break; default: throw new ArgumentOutOfRangeException(); } Log.InfoFormat("Upload do clã {0} de {1}: {2}@{3} em {4:N1}s...", i + 1, clans.Length, clan.ClanTag, clan.Plataform, fsw.Elapsed.TotalSeconds); } Interlocked.Increment(ref doneCount); Log.InfoFormat("Processado clã {0} de {1}: {2}@{3} em {4:N1}s. {5} totais.", i + 1, clans.Length, clan.ClanTag, clan.Plataform, csw.Elapsed.TotalSeconds, doneCount); }); var calculationTime = sw.Elapsed; // Envia o e-mail de status try { siteStatusXbox = fetcher.GetSiteDiagnostic(ConfigurationManager.AppSettings["RemoteSiteStatusApi"], ConfigurationManager.AppSettings["ApiAdminKey"]); siteStatusPs = fetcher.GetSiteDiagnostic(ConfigurationManager.AppSettings["PsRemoteSiteStatusApi"], ConfigurationManager.AppSettings["ApiAdminKey"]); } catch (Exception ex) { Log.Error("Error getting final site diagnostics", ex); // The site is offline... siteStatusXbox = siteStatusPs = new SiteDiagnostic(null); } Log.Debug("Obtendo informações do BD..."); var dd = provider.GetDataDiagnostic(); Log.InfoFormat("Filas: {0} jogadores; {1} clans; {2} cálculos.", dd.PlayersQueueLenght, dd.MembershipQueueLenght, dd.CalculateQueueLenght); Log.InfoFormat( "{0} jogadores; {1:N0} por dia; {2:N0} por hora; reais: {3:N0}; {4:N0}; {5:N0}; {6:N0}", dd.TotalPlayers, dd.ScheduledPlayersPerDay, dd.ScheduledPlayersPerHour, dd.AvgPlayersPerHourLastDay, dd.AvgPlayersPerHourLast6Hours, dd.AvgPlayersPerHourLast2Hours, dd.AvgPlayersPerHourLastHour); Log.Debug("Enviando e-mail..."); mailSender.SendStatusMessage(siteStatusXbox, siteStatusPs, dd, minPerDay, maxPerDay, calculationTime, doneCount, timedOut); if (dd.ScheduledPlayersPerDay < minPerDay || dd.ScheduledPlayersPerDay > maxPerDay) { recorder.BalanceClanSchedule(minPerDay, maxPerDay); } Log.DebugFormat( "DateTime.UtcNow: {0:o}; hourToDeleteOldFiles: {1}; daysToKeepOnDelete: {2}", DateTime.UtcNow, hourToDeleteOldFiles, daysToKeepOnDelete); // Espera o cálculo e a deleção terminarem Task.WaitAll(calculationTask, deleteTask); // Tempo extra para os e-mails terminarem de serem enviados Thread.Sleep(TimeSpan.FromMinutes(2)); Log.InfoFormat("CalculateClanStats terminando normalmente para {1} clãs em {0}.", sw.Elapsed, clans.Length); return(0); } catch (Exception ex) { Log.Fatal(ex); Console.WriteLine(ex); return(1); } }