Оптимизирована работа с памятью

This commit is contained in:
2026-04-17 19:14:38 +03:00
parent c3056010eb
commit e06b55ad07
7 changed files with 184 additions and 124 deletions

Binary file not shown.

View File

@@ -564,6 +564,20 @@ impl LogoRegistry {
log::warn!("No logo found for team='{}', sport='{}'", team, sport); log::warn!("No logo found for team='{}', sport='{}'", team, sport);
None None
} }
/// Оставляет только указанные команды, удаляя все остальные
pub fn retain<F>(&mut self, mut predicate: F)
where
F: FnMut(&str, &str) -> bool,
{
self.logos.retain(|(team, sport), _| predicate(team, sport));
}
/// Возвращает количество записей в реестре
pub fn len(&self) -> usize {
self.logos.len()
}
} }
fn parse_date(s: &str) -> (NaiveDate, String) { fn parse_date(s: &str) -> (NaiveDate, String) {

View File

@@ -274,35 +274,8 @@ fn display_workbook_structure(workbook: &ExcelWorkbook) {
} }
} }
async fn generate_and_submit_jobs( // Вспомогательная функция для поиска значения по части ключа
workbook: &ExcelWorkbook, fn get_cell_fuzzy(row: &HashMap<String, String>, key_part: &str) -> Option<String> {
config: &Config,
) -> Result<Vec<(String, String)>> {
info!("Preparing Nexrender jobs...");
let start_sheet = workbook
.get_sheet("Start")
.ok_or_else(|| anyhow!("Sheet 'Start' not found"))?;
let sport_sheet = workbook
.get_sheet("SPORT")
.ok_or_else(|| anyhow!("Sheet 'SPORT' not found"))?;
let teams_sheet = workbook
.get_sheet("TEAMS")
.ok_or_else(|| anyhow!("Sheet 'TEAMS' not found"))?;
let channel_sheet = workbook
.get_sheet("CHANELL")
.ok_or_else(|| anyhow!("Sheet 'CHANELL' not found"))?;
// Загружаем sport packs
let packs: HashMap<String, String> = sport_sheet
.rows
.iter()
.filter_map(|row| Some((row.get("SPORT")?.clone(), row.get("LINK")?.clone())))
.collect();
info!("Loaded {} sport packs", packs.len());
// Вспомогательная функция для поиска значения по части ключа
fn get_cell_fuzzy(row: &HashMap<String, String>, key_part: &str) -> Option<String> {
if let Some(val) = row.get(key_part) { if let Some(val) = row.get(key_part) {
if !val.is_empty() { if !val.is_empty() {
return Some(val.clone()); return Some(val.clone());
@@ -314,13 +287,42 @@ async fn generate_and_submit_jobs(
} }
} }
None None
} }
async fn generate_and_submit_jobs(
workbook: &ExcelWorkbook,
config: &Config,
) -> Result<Vec<(String, String)>> {
info!("Preparing Nexrender jobs...");
// ========== ЗАГРУЖАЕМ SPORT PACKS ==========
let sport_sheet = workbook
.get_sheet("SPORT")
.ok_or_else(|| anyhow!("Sheet 'SPORT' not found"))?;
let packs: HashMap<String, String> = sport_sheet
.rows
.iter()
.filter_map(|row| Some((row.get("SPORT")?.clone(), row.get("LINK")?.clone())))
.collect();
info!("Loaded {} sport packs", packs.len());
// sport_sheet автоматически освободится при выходе из функции
// Лист SPORT больше не нужен
let sport_rows_count = sport_sheet.rows.len();
debug!("Dropped SPORT sheet ({} rows)", sport_rows_count);
// ========== ЗАГРУЖАЕМ TEAM LOGOS ==========
let teams_sheet = workbook
.get_sheet("TEAMS")
.ok_or_else(|| anyhow!("Sheet 'TEAMS' not found"))?;
// Загружаем team logos
debug!("TEAMS headers: {:?}", teams_sheet.headers); debug!("TEAMS headers: {:?}", teams_sheet.headers);
if log_enabled!(Level::Debug) {
for (i, row) in teams_sheet.rows.iter().take(3).enumerate() { for (i, row) in teams_sheet.rows.iter().take(3).enumerate() {
debug!("TEAMS row {}: {:?}", i, row); debug!("TEAMS row {}: {:?}", i, row);
} }
}
let mut logos = LogoRegistry::with_capacity(teams_sheet.rows.len()); let mut logos = LogoRegistry::with_capacity(teams_sheet.rows.len());
for row in &teams_sheet.rows { for row in &teams_sheet.rows {
@@ -332,7 +334,8 @@ async fn generate_and_submit_jobs(
logos.insert(team.clone(), sport.clone(), link.clone()); logos.insert(team.clone(), sport.clone(), link.clone());
} }
} }
info!("Loaded {} team logos", teams_sheet.rows.len()); let total_teams = teams_sheet.rows.len();
info!("Loaded {} team logos", total_teams);
// Дебаг: выводим статистику по TEAMS // Дебаг: выводим статистику по TEAMS
if log_enabled!(Level::Debug) { if log_enabled!(Level::Debug) {
@@ -343,19 +346,24 @@ async fn generate_and_submit_jobs(
.collect(); .collect();
debug!("Sports found in TEAMS sheet: {:?}", sports); debug!("Sports found in TEAMS sheet: {:?}", sports);
for sport in sports.iter() { for sport in sports.iter().take(5) {
let examples: Vec<_> = teams_sheet let examples: Vec<_> = teams_sheet
.rows .rows
.iter() .iter()
.filter(|r| r.get("SPORT") == Some(*sport)) .filter(|r| r.get("SPORT") == Some(*sport))
.take(5) .take(3)
.filter_map(|r| r.get("TEAM")) .filter_map(|r| r.get("TEAM"))
.collect(); .collect();
debug!(" TEAMS examples for '{}': {:?}", sport, examples); debug!(" TEAMS examples for '{}': {:?}", sport, examples);
} }
} }
// Загружаем channel logos
// ========== ЗАГРУЖАЕМ CHANNEL LOGOS ==========
let channel_sheet = workbook
.get_sheet("CHANELL")
.ok_or_else(|| anyhow!("Sheet 'CHANELL' not found"))?;
let channels: HashMap<String, String> = channel_sheet let channels: HashMap<String, String> = channel_sheet
.rows .rows
.iter() .iter()
@@ -367,19 +375,49 @@ async fn generate_and_submit_jobs(
debug!("Channels loaded: {:?}", channels.keys().collect::<Vec<_>>()); debug!("Channels loaded: {:?}", channels.keys().collect::<Vec<_>>());
} }
let mut jobs: Vec<JobData> = Vec::with_capacity(start_sheet.rows.len() * 3);
// Собираем статистику по ненайденным логотипам для дебага // ========== ОБРАБАТЫВАЕМ START (только активные строки) ==========
let start_sheet = workbook
.get_sheet("Start")
.ok_or_else(|| anyhow!("Sheet 'Start' not found"))?;
let total_start_rows = start_sheet.rows.len();
// Сразу фильтруем только строки с STATE = "FALSE"
let active_rows: Vec<(usize, &HashMap<String, String>)> = start_sheet
.rows
.iter()
.enumerate()
.filter(|(_, row)| row.get("STATE").map(|s| s.as_str()) == Some("FALSE"))
.collect();
info!(
"Found {} active rows (STATE='FALSE') out of {} total",
active_rows.len(),
total_start_rows
);
// Собираем использованные команды для очистки logos
let mut used_teams: HashSet<(String, String)> = HashSet::new();
let mut jobs: Vec<JobData> = Vec::with_capacity(active_rows.len() * 3);
// Для дебага
let mut missing_teams: HashSet<String> = HashSet::new(); let mut missing_teams: HashSet<String> = HashSet::new();
let mut missing_sports: HashSet<String> = HashSet::new(); let mut missing_sports: HashSet<String> = HashSet::new();
for (idx, row) in start_sheet.rows.iter().enumerate() { for (idx, row) in active_rows {
if let Some(state) = row.get("STATE") {
if state == "FALSE" {
let team_a = row.get("TEAM A").cloned().unwrap_or_default(); let team_a = row.get("TEAM A").cloned().unwrap_or_default();
let team_b = row.get("TEAM B").cloned().unwrap_or_default(); let team_b = row.get("TEAM B").cloned().unwrap_or_default();
let sport = row.get("SPORT").cloned().unwrap_or_default(); let sport = row.get("SPORT").cloned().unwrap_or_default();
// Запоминаем использованные команды
if !team_a.is_empty() {
used_teams.insert((team_a.clone(), sport.clone()));
}
if !team_b.is_empty() {
used_teams.insert((team_b.clone(), sport.clone()));
}
if log_enabled!(Level::Debug) { if log_enabled!(Level::Debug) {
let logo_a = logos.find(&team_a, &sport); let logo_a = logos.find(&team_a, &sport);
let logo_b = logos.find(&team_b, &sport); let logo_b = logos.find(&team_b, &sport);
@@ -398,8 +436,16 @@ async fn generate_and_submit_jobs(
jobs.extend(job.create_variants()); jobs.extend(job.create_variants());
} }
} }
}
} // Очищаем logos от неиспользуемых команд
let logos_before = logos.len();
logos.retain(|team, sport| used_teams.contains(&(team.to_string(), sport.to_string())));
info!(
"Retained {} used team logos (cleaned up {} unused)",
logos.len(),
logos_before - logos.len()
);
// Дебаг: выводим сводку по отсутствующим логотипам // Дебаг: выводим сводку по отсутствующим логотипам
if log_enabled!(Level::Debug) && !missing_teams.is_empty() { if log_enabled!(Level::Debug) && !missing_teams.is_empty() {
@@ -420,22 +466,23 @@ async fn generate_and_submit_jobs(
return Ok(Vec::new()); return Ok(Vec::new());
} }
cleanup_finished_jobs(&config.nexrender_api_url).await?; // ========== ОТПРАВКА ЗАДАНИЙ ==========
cleanup_finished_jobs(&config.nexrender_api_url).await?;
let http_client = Client::new(); let http_client = Client::new();
let mut submitted_jobs: Vec<(String, String)> = Vec::with_capacity(jobs.len()); let mut submitted_jobs: Vec<(String, String)> = Vec::with_capacity(jobs.len());
// Отправляем задания группами по 3 (оригинал + today + tomorrow) // Запускаем ВСЕ группы параллельно
for chunk in jobs.chunks(3) { let mut all_tasks = Vec::new();
let mut tasks = Vec::with_capacity(3);
for chunk in jobs.chunks(3) {
for job in chunk { for job in chunk {
let client = http_client.clone(); let client = http_client.clone();
let api_url = config.nexrender_api_url.clone(); let api_url = config.nexrender_api_url.clone();
let config_clone = config.clone(); let config_clone = config.clone();
let job_owned = job.clone(); let job_owned = job.clone();
tasks.push(tokio::spawn(async move { all_tasks.push(tokio::spawn(async move {
let nexrender_job = job_owned.to_nexrender_job(&config_clone); let nexrender_job = job_owned.to_nexrender_job(&config_clone);
info!("Submitting job: {}", job_owned.outfile_name); info!("Submitting job: {}", job_owned.outfile_name);
@@ -463,9 +510,10 @@ async fn generate_and_submit_jobs(
} }
})); }));
} }
}
// Ждём завершения группы // Ждём завершения ВСЕХ задач
for task in tasks { for task in all_tasks {
match task.await { match task.await {
Ok(Ok(Some((uid, outfile_name)))) => { Ok(Ok(Some((uid, outfile_name)))) => {
info!("Job submitted: {} (UID: {})", outfile_name, uid); info!("Job submitted: {} (UID: {})", outfile_name, uid);
@@ -481,9 +529,7 @@ async fn generate_and_submit_jobs(
error!("Task join error: {}", e); error!("Task join error: {}", e);
} }
} }
} }
}
info!( info!(
"Successfully submitted {} jobs to Nexrender", "Successfully submitted {} jobs to Nexrender",
submitted_jobs.len() submitted_jobs.len()