/************************************************************************* * UrBackup - Client/Server backup system * Copyright (C) 2011-2016 Martin Raiber * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . **************************************************************************/ #include "server_update_stats.h" #include "database.h" #include "../Interface/Server.h" #include "../Interface/Database.h" #include "../Interface/Query.h" #include "../Interface/File.h" #include "../urbackupcommon/os_functions.h" #include "../stringtools.h" #include "server_settings.h" #include "server_status.h" #include "ClientMain.h" #include "../Interface/DatabaseCursor.h" #include "create_files_index.h" #include "dao/ServerFilesDao.h" #include ServerUpdateStats::ServerUpdateStats(bool image_repair_mode, bool interruptible) : image_repair_mode(image_repair_mode), interruptible(interruptible) { } void ServerUpdateStats::createQueries(void) { q_get_images=db->Prepare("SELECT id,clientid,path FROM backup_images WHERE complete=1 AND runningPrepare("UPDATE clients SET bytes_used_images=? WHERE id=?", false); q_get_sizes=db->Prepare("SELECT id, bytes_used_files FROM clients", false); q_size_update=db->Prepare("UPDATE clients SET bytes_used_files=? WHERE id=?", false); q_update_backups=db->Prepare("UPDATE backups SET size_bytes=? WHERE id=?", false); q_get_backup_size=db->Prepare("SELECT size_bytes FROM backups WHERE id=?", false); q_get_del_size=db->Prepare("SELECT delsize FROM del_stats WHERE backupid=? AND image=0 AND created>datetime('now','-4 days')", false); q_add_del_size=db->Prepare("INSERT INTO del_stats (backupid, image, delsize, clientid, incremental, stoptime) VALUES (?, 0, ?, ?, ?, CURRENT_TIMESTAMP)", false); q_update_del_size=db->Prepare("UPDATE del_stats SET delsize=?,stoptime=CURRENT_TIMESTAMP WHERE backupid=? AND image=0 AND created>datetime('now','-4 days')", false); q_save_client_hist=db->Prepare("INSERT INTO clients_hist (id, name, lastbackup, lastseen, lastbackup_image, bytes_used_files, bytes_used_images, hist_id) SELECT id, name, lastbackup, lastseen, lastbackup_image, bytes_used_files, bytes_used_images, ? AS hist_id FROM clients", false); q_set_file_backup_null=db->Prepare("UPDATE backups SET size_bytes=0 WHERE size_bytes=-1 AND complete=1", false); q_create_hist=db->Prepare("INSERT INTO clients_hist_id (created) VALUES (CURRENT_TIMESTAMP)", false); q_get_all_clients=db->Prepare("SELECT id FROM clients", false); } void ServerUpdateStats::destroyQueries(void) { db->destroyQuery(q_get_images); db->destroyQuery(q_update_images_size); db->destroyQuery(q_get_sizes); db->destroyQuery(q_size_update); db->destroyQuery(q_update_backups); db->destroyQuery(q_get_backup_size); db->destroyQuery(q_get_del_size); db->destroyQuery(q_add_del_size); db->destroyQuery(q_update_del_size); db->destroyQuery(q_save_client_hist); db->destroyQuery(q_set_file_backup_null); db->destroyQuery(q_create_hist); db->destroyQuery(q_get_all_clients); } void ServerUpdateStats::operator()(void) { if(interruptible) { if( ClientMain::getNumberOfRunningFileBackups()>0 ) { return; } } db=Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER); backupdao.reset(new ServerBackupDao(db)); fileindex.reset(create_lmdb_files_index()); ServerSettings server_settings(db); db_results cache_res; if(db->getEngineName()=="sqlite") { cache_res=db->Read("PRAGMA cache_size"); db->Write("PRAGMA cache_size = -"+convert(server_settings.getSettings()->update_stats_cachesize)); } createQueries(); if(!image_repair_mode) { q_create_hist->Write(); q_create_hist->Reset(); q_save_client_hist->Bind(db->getLastInsertID()); q_save_client_hist->Write(); q_save_client_hist->Reset(); } update_images(); if(!image_repair_mode) { update_files(); q_create_hist->Write(); q_create_hist->Reset(); q_save_client_hist->Bind(db->getLastInsertID()); q_save_client_hist->Write(); q_save_client_hist->Reset(); q_set_file_backup_null->Write(); q_set_file_backup_null->Reset(); } destroyQueries(); if(!cache_res.empty()) { db->Write("PRAGMA cache_size = "+cache_res[0]["cache_size"]); db->freeMemory(); } backupdao.reset(); } void ServerUpdateStats::repairImages(void) { ServerUpdateStats sus(true); sus(); } void ServerUpdateStats::update_images(void) { if(!image_repair_mode) Server->Log("Updating image stats...",LL_INFO); db_results res=q_get_images->Read(); q_get_images->Reset(); std::map clients_used; db_results all_clients=q_get_all_clients->Read(); q_get_all_clients->Reset(); for(size_t i=0;iopenFile(os_file_prefix(fn), MODE_READ); if(file==NULL) { bool b=repairImagePath(res[i]); if(b) { update_images(); return; } Server->Log("Error opening file '"+fn+"'", LL_ERROR); continue; } int cid=watoi(res[i]["clientid"]); std::map::iterator it=clients_used.find(cid); if(it==clients_used.end()) { clients_used.insert(std::pair(cid, file->RealSize())); } else { it->second+=file->RealSize(); } Server->destroy(file); } for(std::map::iterator it=clients_used.begin();it!=clients_used.end();++it) { q_update_images_size->Bind(it->second); q_update_images_size->Bind(it->first); q_update_images_size->Write(); q_update_images_size->Reset(); } } void ServerUpdateStats::measureSpeed(void) { int64 ttime=Server->getTimeMS(); static int64 lasttime=ttime; if(lasttime!=ttime) { float speed=num_updated_files/((ttime-lasttime)/1000.f); Server->Log("File processing speed: "+convert(speed)+" files/s", LL_INFO); num_updated_files=0; lasttime=ttime; } } void ServerUpdateStats::update_files(void) { num_updated_files=0; Server->Log("Updating file statistics..."); IDatabase* files_db = Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER_FILES); ServerFilesDao filesdao(files_db); size_t total_num = static_cast(filesdao.getIncomingStatsCount().value); size_t total_i=0; std::map size_data_clients=getFilebackupSizesClients(); std::map size_data_backups; std::map del_sizes; DBScopedSynchronous synchonous_db(db); DBScopedSynchronous synchonous_files_db(files_db); DBScopedWriteTransaction files_db_transaction(NULL); bool started_transaction = false; std::vector stat_entries; int last_pc=0; do { if(interruptible) { if( ClientMain::getNumberOfRunningFileBackups()>0 ) { files_db_transaction.rollback(); return; } } ServerStatus::updateActive(); stat_entries = filesdao.getIncomingStats(); if(!started_transaction && !stat_entries.empty()) { files_db_transaction.reset(files_db); started_transaction = true; } for(size_t i=0;i0) { int pc=(std::min)(100, (int)((float)total_i/(float)total_num*100.f+0.5f)); if(pc!=last_pc) { measureSpeed(); Server->Log( "Updating file statistics: "+convert(pc)+"%", LL_INFO); last_pc=pc; } } ServerFilesDao::SIncomingStat& entry = stat_entries[i]; std::vector clients; std::vector s_clients; Tokenize(entry.existing_clients, s_clients, ","); clients.resize(s_clients.size()); for(size_t j=0;j::iterator it_client = std::find(clients.begin(), clients.end(), entry.clientid); if(it_client!=clients.end()) { clients.erase(it_client); } if(!clients.empty()) { current_size_per_client = entry.filesize/clients.size(); add(clients, current_size_per_client, size_data_clients); } if(entry.direction!= ServerFilesDao::c_direction_outgoing_nobackupstat) { add_del(del_sizes, entry.backupid, entry.filesize, entry.clientid, entry.incremental); } } else { Server->Log("Unknown direction in ServerUpdateStats::update_files " + convert((int)entry.direction), LL_ERROR); assert(false); } filesdao.delIncomingStatEntry(entry.id); } } while(!stat_entries.empty()); DBScopedWriteTransaction db_transaction(db); updateSizes(size_data_clients); updateDels(del_sizes); updateBackups(size_data_backups); db->Write("UPDATE backups SET size_calculated=1 WHERE size_calculated=0 AND done=1"); } std::map ServerUpdateStats::getFilebackupSizesClients(void) { std::map ret; db_results res=q_get_sizes->Read(); q_get_sizes->Reset(); for(size_t i=0;i(watoi(res[i]["id"]), os_atoi64(res[i]["bytes_used_files"]))); } return ret; } void ServerUpdateStats::updateSizes(std::map & size_data) { for(std::map::iterator it=size_data.begin();it!=size_data.end();++it) { q_size_update->Bind(it->second); q_size_update->Bind(it->first); q_size_update->Write(); q_size_update->Reset(); } } void ServerUpdateStats::add(const std::vector& subset, int64 num, std::map &data) { for(size_t i=0;i &data, int backupid, _i64 filesize) { std::map::iterator it=data.find(backupid); if(it==data.end()) { q_get_backup_size->Bind(backupid); db_results res=q_get_backup_size->Read(); q_get_backup_size->Reset(); if(!res.empty()) { _i64 b_fs=os_atoi64(res[0]["size_bytes"]); if(b_fs!=-1) { filesize+=b_fs; } } data.insert(std::pair(backupid, filesize)); } else { it->second+=filesize; } } void ServerUpdateStats::updateBackups(std::map &data) { for(std::map::iterator it=data.begin();it!=data.end();++it) { q_update_backups->Bind(it->second); q_update_backups->Bind(it->first); q_update_backups->Write(); q_update_backups->Reset(); } } void ServerUpdateStats::add_del(std::map &data, int backupid, _i64 filesize, int clientid, int incremental) { std::map::iterator it=data.find(backupid); if(it==data.end()) { q_get_del_size->Bind(backupid); db_results res=q_get_del_size->Read(); q_get_del_size->Reset(); if(!res.empty()) { filesize+=os_atoi64(res[0]["delsize"]); } SDelInfo di; di.delsize=filesize; di.clientid=clientid; di.incremental=incremental; data.insert(std::pair(backupid, di)); } else { it->second.delsize+=filesize; } } void ServerUpdateStats::updateDels(std::map &data) { for(std::map::iterator it=data.begin();it!=data.end();++it) { q_get_del_size->Bind(it->first); db_results res=q_get_del_size->Read(); q_get_del_size->Reset(); if(res.empty()) { q_add_del_size->Bind(it->first); q_add_del_size->Bind(it->second.delsize); q_add_del_size->Bind(it->second.clientid); q_add_del_size->Bind(it->second.incremental); q_add_del_size->Write(); q_add_del_size->Reset(); } else { q_update_del_size->Bind(it->second.delsize); q_update_del_size->Bind(it->first); q_update_del_size->Write(); q_update_del_size->Reset(); } } } bool ServerUpdateStats::repairImagePath(str_map img) { int clientid=watoi(img["clientid"]); ServerSettings settings(db, clientid); IQuery *q=db->Prepare("SELECT name FROM clients WHERE id=?", false); q->Bind(clientid); db_results res=q->Read(); q->Reset(); db->destroyQuery(q); if(!res.empty()) { std::string clientname=res[0]["name"]; std::string imgname=ExtractFileName(img["path"]); std::string backuppath_single = ExtractFileName(ExtractFilePath(img["path"])); std::string new_name; if (backuppath_single != clientname) { new_name = settings.getSettings()->backupfolder + os_file_sep() + clientname + os_file_sep() + backuppath_single + os_file_sep() + imgname; } else { new_name = settings.getSettings()->backupfolder + os_file_sep() + clientname + os_file_sep() + imgname; } IFile * file=Server->openFile(os_file_prefix(new_name), MODE_READ); if(file==NULL) { Server->Log("Repairing image failed", LL_INFO); return false; } Server->destroy(file); q=db->Prepare("UPDATE backup_images SET path=? WHERE id=?", false); q->Bind(new_name); q->Bind(img["id"]); q->Write(); q->Reset(); db->destroyQuery(q); return true; } return false; }