changes to AutoBackModule. Time interval is now unique and only definable on OpenSim.ini. All enabled regions will be saved in sequence whne it expires. Interval is time since last region save end, so will not follow wall clock. This reduces the chance of overlapped saves. Console command renamed to dooarbackup to be more distint from db backup. Lost the region load checks, to put back later

0.9.0-post-fixes
UbitUmarov 2017-06-21 03:31:07 +01:00
parent 79e166e9aa
commit 651952e01e
2 changed files with 224 additions and 559 deletions

View File

@ -59,23 +59,20 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Config Settings Documentation. /// Config Settings Documentation.
/// Each configuration setting can be specified in two places: OpenSim.ini or Regions.ini. /// Configuration setting can be specified in two places: OpenSim.ini and/or Regions.ini.
/// If specified in Regions.ini, the settings should be within the region's section name.
/// If specified in OpenSim.ini, the settings should be within the [AutoBackupModule] section.
/// Region-specific settings take precedence.
/// ///
/// AutoBackupModuleEnabled: True/False. Default: False. If True, use the auto backup module. This setting does not support per-region basis. /// OpenSim.ini only settings section [AutoBackupModule]
/// All other settings under [AutoBackupModule] are ignored if AutoBackupModuleEnabled is false, even per-region settings! /// AutoBackupModuleEnabled: True/False. Default: False. If True, use the auto backup module.
/// AutoBackup: True/False. Default: False. If True, activate auto backup functionality. /// if false module is disable and all rest is ignored
/// This is the only required option for enabling auto-backup; the other options have sane defaults.
/// If False for a particular region, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored.
/// If False globally (the default), only regions that specifically override it in Regions.ini will get AutoBackup functionality.
/// AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours). /// AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours).
/// The number of minutes between each backup attempt. /// The number of minutes between each backup attempt.
/// If a negative or zero value is given, it is equivalent to setting AutoBackup = False. ///
/// AutoBackupBusyCheck: True/False. Default: True. /// Next can be set on OpenSim.ini, as default, and or per region in Regions.ini
/// If True, we will only take an auto-backup if a set of conditions are met. /// Region-specific settings take precedence.
/// These conditions are heuristics to try and avoid taking a backup when the sim is busy. ///
/// AutoBackup: True/False. Default: False. If True, activate auto backup functionality.
/// controls backup per region, with default optionaly set on OpenSim.ini
/// AutoBackupSkipAssets /// AutoBackupSkipAssets
/// If true, assets are not saved to the oar file. Considerably reduces impact on simulator when backing up. Intended for when assets db is backed up separately /// If true, assets are not saved to the oar file. Considerably reduces impact on simulator when backing up. Intended for when assets db is backed up separately
/// AutoBackupKeepFilesForDays /// AutoBackupKeepFilesForDays
@ -93,36 +90,22 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
/// "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file. /// "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file.
/// AutoBackupDir: String. Default: "." (the current directory). /// AutoBackupDir: String. Default: "." (the current directory).
/// A directory (absolute or relative) where backups should be saved. /// A directory (absolute or relative) where backups should be saved.
/// AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass.
/// If the time dilation is below this value, don't take a backup right now.
/// AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass.
/// If the number of agents is greater than this value, don't take a backup right now
/// Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions.
/// Also helps if you don't want AutoBackup at all.
/// </remarks> /// </remarks>
[Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "AutoBackupModule")] [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "AutoBackupModule")]
public class AutoBackupModule : ISharedRegionModule public class AutoBackupModule : ISharedRegionModule
{ {
private static readonly ILog m_log = private static readonly ILog m_log =
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private readonly Dictionary<Guid, IScene> m_pendingSaves = new Dictionary<Guid, IScene>(1);
private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState(); private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState();
private readonly Dictionary<IScene, AutoBackupModuleState> m_states = private readonly Dictionary<IScene, AutoBackupModuleState> m_states =
new Dictionary<IScene, AutoBackupModuleState>(1); new Dictionary<IScene, AutoBackupModuleState>(1);
private readonly Dictionary<Timer, List<IScene>> m_timerMap =
new Dictionary<Timer, List<IScene>>(1);
private readonly Dictionary<double, Timer> m_timers = new Dictionary<double, Timer>(1);
private delegate T DefaultGetter<T>(string settingName, T defaultValue); private delegate T DefaultGetter<T>(string settingName, T defaultValue);
private bool m_enabled; private bool m_enabled;
private ICommandConsole m_console; private ICommandConsole m_console;
private List<Scene> m_Scenes = new List<Scene> (); private List<Scene> m_Scenes = new List<Scene> ();
private Timer m_masterTimer;
private bool m_busy;
/// <summary>
/// Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState!
/// </summary>
private bool m_closed;
private IConfigSource m_configSource; private IConfigSource m_configSource;
@ -159,36 +142,34 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
void IRegionModuleBase.Initialise(IConfigSource source) void IRegionModuleBase.Initialise(IConfigSource source)
{ {
// Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module // Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module
this.m_configSource = source; m_configSource = source;
IConfig moduleConfig = source.Configs["AutoBackupModule"]; IConfig moduleConfig = source.Configs["AutoBackupModule"];
if (moduleConfig == null) if (moduleConfig == null)
{ {
this.m_enabled = false; m_enabled = false;
return; return;
} }
else
{
this.m_enabled = moduleConfig.GetBoolean("AutoBackupModuleEnabled", false);
if (this.m_enabled)
{
m_log.Info("[AUTO BACKUP]: AutoBackupModule enabled");
}
else
{
return;
}
}
Timer defTimer = new Timer(43200000); m_enabled = moduleConfig.GetBoolean("AutoBackupModuleEnabled", false);
this.m_defaultState.Timer = defTimer; if(!m_enabled)
this.m_timers.Add(43200000, defTimer); return;
defTimer.Elapsed += this.HandleElapsed;
defTimer.AutoReset = true;
defTimer.Start();
AutoBackupModuleState abms = this.ParseConfig(null, true); m_log.Info("[AUTO BACKUP]: AutoBackupModule enabled");
m_log.Debug("[AUTO BACKUP]: Here is the default config:"); m_masterTimer = new Timer(43200000);
m_log.Debug(abms.ToString()); m_masterTimer.Elapsed += HandleElapsed;
m_masterTimer.AutoReset = false;
ParseDefaultConfig();
m_log.Debug("[AUTO BACKUP]: Default config:");
m_log.Debug(m_defaultState.ToString());
m_console = MainConsole.Instance;
m_console.Commands.AddCommand (
"AutoBackup", true, "dooarbackup",
"dooarbackup <regionName>",
"do single region backup into a oar. Identical to save oar but using AutoBackup settings for name etc", DoBackup);
m_busy = true;
} }
/// <summary> /// <summary>
@ -196,13 +177,11 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
/// </summary> /// </summary>
void IRegionModuleBase.Close() void IRegionModuleBase.Close()
{ {
if (!this.m_enabled) if (!m_enabled)
{
return; return;
}
// We don't want any timers firing while the sim's coming down; strange things may happen. // We don't want any timers firing while the sim's coming down; strange things may happen.
this.StopAllTimers(); m_masterTimer.Dispose();
} }
/// <summary> /// <summary>
@ -211,18 +190,11 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
/// <param name="scene"></param> /// <param name="scene"></param>
void IRegionModuleBase.AddRegion (Scene scene) void IRegionModuleBase.AddRegion (Scene scene)
{ {
if (!this.m_enabled) { if (!m_enabled)
return; return;
}
lock (m_Scenes) {
m_Scenes.Add (scene);
}
m_console = MainConsole.Instance;
m_console.Commands.AddCommand ( lock (m_Scenes)
"AutoBackup", false, "dobackup", m_Scenes.Add (scene);
"dobackup",
"do backup.", DoBackup);
} }
/// <summary> /// <summary>
@ -231,28 +203,14 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
/// <param name="scene">The scene (region) to stop performing AutoBackup on.</param> /// <param name="scene">The scene (region) to stop performing AutoBackup on.</param>
void IRegionModuleBase.RemoveRegion(Scene scene) void IRegionModuleBase.RemoveRegion(Scene scene)
{ {
if (!this.m_enabled) if (m_enabled)
{
return; return;
}
m_Scenes.Remove (scene); lock(m_Scenes)
if (this.m_states.ContainsKey(scene))
{ {
AutoBackupModuleState abms = this.m_states[scene]; if (m_states.ContainsKey(scene))
m_states.Remove(scene);
// Remove this scene out of the timer map list m_Scenes.Remove(scene);
Timer timer = abms.Timer;
List<IScene> list = this.m_timerMap[timer];
list.Remove(scene);
// Shut down the timer if this was the last scene for the timer
if (list.Count == 0)
{
this.m_timerMap.Remove(timer);
this.m_timers.Remove(timer.Interval);
timer.Close();
}
this.m_states.Remove(scene);
} }
} }
@ -263,22 +221,29 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
/// <param name="scene">The scene to (possibly) perform AutoBackup on.</param> /// <param name="scene">The scene to (possibly) perform AutoBackup on.</param>
void IRegionModuleBase.RegionLoaded(Scene scene) void IRegionModuleBase.RegionLoaded(Scene scene)
{ {
if (!this.m_enabled) if (!m_enabled)
{
return; return;
}
// This really ought not to happen, but just in case, let's pretend it didn't... // This really ought not to happen, but just in case, let's pretend it didn't...
if (scene == null) if (scene == null)
{
return; return;
AutoBackupModuleState abms = ParseConfig(scene);
if(abms == null)
{
m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName);
m_log.Debug("DEFAULT");
abms = new AutoBackupModuleState(m_defaultState);
}
else
{
m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName);
m_log.Debug(abms.ToString());
} }
AutoBackupModuleState abms = this.ParseConfig(scene, false);
m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName);
m_log.Debug((abms == null ? "DEFAULT" : abms.ToString()));
m_states.Add(scene, abms); m_states.Add(scene, abms);
m_busy = false;
m_masterTimer.Start();
} }
/// <summary> /// <summary>
@ -286,30 +251,116 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
/// </summary> /// </summary>
void ISharedRegionModule.PostInitialise() void ISharedRegionModule.PostInitialise()
{ {
} }
#endregion #endregion
private void DoBackup (string module, string[] args) private void DoBackup (string module, string[] args)
{ {
if (args.Length != 2) { if (!m_enabled)
MainConsole.Instance.OutputFormat ("Usage: dobackup <regionname>"); return;
if(m_busy)
{
MainConsole.Instance.OutputFormat ("Already doing a backup, please try later");
return; return;
} }
if (args.Length != 2)
{
MainConsole.Instance.OutputFormat ("Usage: dooarbackup <regionname>");
return;
}
m_busy = true;
bool found = false; bool found = false;
string name = args [1]; string name = args [1];
lock (m_Scenes) { Scene[] scenes;
foreach (Scene s in m_Scenes) { lock (m_Scenes)
string test = s.Name.ToString (); scenes = m_Scenes.ToArray();
if (test == name) {
if(scenes == null)
return;
Scene s;
try
{
for(int i = 0; i < scenes.Length; i++)
{
s = scenes[i];
if (s.Name == name)
{
found = true; found = true;
DoRegionBackup (s); m_masterTimer.Stop();
DoRegionBackup(s);
break;
} }
} }
if (!found) { } catch { }
if (!found)
MainConsole.Instance.OutputFormat ("No such region {0}. Nothing to backup", name); MainConsole.Instance.OutputFormat ("No such region {0}. Nothing to backup", name);
m_busy = false;
}
private void ParseDefaultConfig()
{
IConfig config = m_configSource.Configs["AutoBackupModule"];
if (config == null)
return;
// Borrow an existing timer if one exists for the same interval; otherwise, make a new one.
double interval = config.GetDouble("AutoBackupInterval", 720);
interval *= 60000.0;
m_masterTimer.Interval = interval;
m_defaultState.Enabled = config.GetBoolean("AutoBackup", m_defaultState.Enabled);
// Included Option To Skip Assets
m_defaultState.SkipAssets = config.GetBoolean("AutoBackupSkipAssets",m_defaultState.SkipAssets);
// How long to keep backup files in days, 0 Disables this feature
m_defaultState.KeepFilesForDays = config.GetInt("AutoBackupKeepFilesForDays",m_defaultState.KeepFilesForDays);
// Set file naming algorithm
string stmpNamingType = config.GetString("AutoBackupNaming", m_defaultState.NamingType.ToString());
NamingType tmpNamingType;
if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase))
tmpNamingType = NamingType.Time;
else if (stmpNamingType.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase))
tmpNamingType = NamingType.Sequential;
else if (stmpNamingType.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase))
tmpNamingType = NamingType.Overwrite;
else
{
m_log.Warn("Unknown naming type specified for Default");
tmpNamingType = NamingType.Time;
}
m_defaultState.NamingType = tmpNamingType;
m_defaultState.Script = config.GetString("AutoBackupScript", m_defaultState.Script);
string backupDir = config.GetString("AutoBackupDir", ".");
if (backupDir != ".")
{
try
{
DirectoryInfo dirinfo = new DirectoryInfo(backupDir);
if (!dirinfo.Exists)
dirinfo.Create();
}
catch (Exception e)
{
m_log.Warn(
"[AUTO BACKUP]: BAD NEWS. You won't be able to save backups to directory " +
backupDir +
" because it doesn't exist or there's a permissions issue with it. Here's the exception.",
e);
} }
} }
m_defaultState.BackupDir = backupDir;
} }
/// <summary> /// <summary>
@ -319,247 +370,74 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
/// <param name="scene">The scene to look at.</param> /// <param name="scene">The scene to look at.</param>
/// <param name="parseDefault">Whether this call is intended to figure out what we consider the "default" config (applied to all regions unless overridden by per-region settings).</param> /// <param name="parseDefault">Whether this call is intended to figure out what we consider the "default" config (applied to all regions unless overridden by per-region settings).</param>
/// <returns>An AutoBackupModuleState contains most information you should need to know relevant to auto-backup, as applicable to a single region.</returns> /// <returns>An AutoBackupModuleState contains most information you should need to know relevant to auto-backup, as applicable to a single region.</returns>
private AutoBackupModuleState ParseConfig(IScene scene, bool parseDefault) private AutoBackupModuleState ParseConfig(IScene scene)
{ {
if(scene == null)
return null;
string sRegionName; string sRegionName;
string sRegionLabel; string sRegionLabel;
// string prepend; AutoBackupModuleState state = null;
AutoBackupModuleState state;
if (parseDefault) sRegionName = scene.RegionInfo.RegionName;
{ sRegionLabel = sRegionName;
sRegionName = null;
sRegionLabel = "DEFAULT";
// prepend = "";
state = this.m_defaultState;
}
else
{
sRegionName = scene.RegionInfo.RegionName;
sRegionLabel = sRegionName;
// prepend = sRegionName + ".";
state = null;
}
// Read the config settings and set variables. // Read the config settings and set variables.
IConfig regionConfig = (scene != null ? scene.Config.Configs[sRegionName] : null); IConfig regionConfig = scene.Config.Configs[sRegionName];
IConfig config = this.m_configSource.Configs["AutoBackupModule"]; if (regionConfig == null)
if (config == null) return null;
{
// defaultState would be disabled too if the section doesn't exist.
state = this.m_defaultState;
return state;
}
bool tmpEnabled = ResolveBoolean("AutoBackup", this.m_defaultState.Enabled, config, regionConfig); state = new AutoBackupModuleState();
if (state == null && tmpEnabled != this.m_defaultState.Enabled)
//Varies from default state
{
state = new AutoBackupModuleState();
}
if (state != null) state.Enabled = regionConfig.GetBoolean("AutoBackup", m_defaultState.Enabled);
{
state.Enabled = tmpEnabled;
}
// If you don't want AutoBackup, we stop.
if ((state == null && !this.m_defaultState.Enabled) || (state != null && !state.Enabled))
{
return state;
}
else
{
m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED.");
}
// Borrow an existing timer if one exists for the same interval; otherwise, make a new one.
double interval =
this.ResolveDouble("AutoBackupInterval", this.m_defaultState.IntervalMinutes,
config, regionConfig) * 60000.0;
if (state == null && interval != this.m_defaultState.IntervalMinutes * 60000.0)
{
state = new AutoBackupModuleState();
}
if (this.m_timers.ContainsKey(interval))
{
if (state != null)
{
state.Timer = this.m_timers[interval];
}
m_log.Debug("[AUTO BACKUP]: Reusing timer for " + interval + " msec for region " +
sRegionLabel);
}
else
{
// 0 or negative interval == do nothing.
if (interval <= 0.0 && state != null)
{
state.Enabled = false;
return state;
}
if (state == null)
{
state = new AutoBackupModuleState();
}
Timer tim = new Timer(interval);
state.Timer = tim;
//Milliseconds -> minutes
this.m_timers.Add(interval, tim);
tim.Elapsed += this.HandleElapsed;
tim.AutoReset = true;
tim.Start();
}
// Add the current region to the list of regions tied to this timer.
if (scene != null)
{
if (state != null)
{
if (this.m_timerMap.ContainsKey(state.Timer))
{
this.m_timerMap[state.Timer].Add(scene);
}
else
{
List<IScene> scns = new List<IScene>(1);
scns.Add(scene);
this.m_timerMap.Add(state.Timer, scns);
}
}
else
{
if (this.m_timerMap.ContainsKey(this.m_defaultState.Timer))
{
this.m_timerMap[this.m_defaultState.Timer].Add(scene);
}
else
{
List<IScene> scns = new List<IScene>(1);
scns.Add(scene);
this.m_timerMap.Add(this.m_defaultState.Timer, scns);
}
}
}
bool tmpBusyCheck = ResolveBoolean("AutoBackupBusyCheck",
this.m_defaultState.BusyCheck, config, regionConfig);
if (state == null && tmpBusyCheck != this.m_defaultState.BusyCheck)
{
state = new AutoBackupModuleState();
}
if (state != null)
{
state.BusyCheck = tmpBusyCheck;
}
// Included Option To Skip Assets // Included Option To Skip Assets
bool tmpSkipAssets = ResolveBoolean("AutoBackupSkipAssets", state.SkipAssets = regionConfig.GetBoolean("AutoBackupSkipAssets", m_defaultState.SkipAssets);
this.m_defaultState.SkipAssets, config, regionConfig);
if (state == null && tmpSkipAssets != this.m_defaultState.SkipAssets)
{
state = new AutoBackupModuleState();
}
if (state != null)
{
state.SkipAssets = tmpSkipAssets;
}
// How long to keep backup files in days, 0 Disables this feature // How long to keep backup files in days, 0 Disables this feature
int tmpKeepFilesForDays = ResolveInt("AutoBackupKeepFilesForDays", state.KeepFilesForDays = regionConfig.GetInt("AutoBackupKeepFilesForDays", m_defaultState.KeepFilesForDays);
this.m_defaultState.KeepFilesForDays, config, regionConfig);
if (state == null && tmpKeepFilesForDays != this.m_defaultState.KeepFilesForDays)
{
state = new AutoBackupModuleState();
}
if (state != null)
{
state.KeepFilesForDays = tmpKeepFilesForDays;
}
// Set file naming algorithm // Set file naming algorithm
string stmpNamingType = ResolveString("AutoBackupNaming", string stmpNamingType = regionConfig.GetString("AutoBackupNaming", m_defaultState.NamingType.ToString());
this.m_defaultState.NamingType.ToString(), config, regionConfig);
NamingType tmpNamingType; NamingType tmpNamingType;
if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase)) if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase))
{
tmpNamingType = NamingType.Time; tmpNamingType = NamingType.Time;
}
else if (stmpNamingType.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase)) else if (stmpNamingType.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase))
{
tmpNamingType = NamingType.Sequential; tmpNamingType = NamingType.Sequential;
}
else if (stmpNamingType.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase)) else if (stmpNamingType.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase))
{
tmpNamingType = NamingType.Overwrite; tmpNamingType = NamingType.Overwrite;
}
else else
{ {
m_log.Warn("Unknown naming type specified for region " + sRegionLabel + ": " + m_log.Warn("Unknown naming type specified for region " + sRegionLabel + ": " +
stmpNamingType); stmpNamingType);
tmpNamingType = NamingType.Time; tmpNamingType = NamingType.Time;
} }
m_defaultState.NamingType = tmpNamingType;
if (state == null && tmpNamingType != this.m_defaultState.NamingType) state.Script = regionConfig.GetString("AutoBackupScript", m_defaultState.Script);
{
state = new AutoBackupModuleState();
}
if (state != null) string tmpBackupDir = regionConfig.GetString("AutoBackupDir", ".");
// Let's give the user some convenience and auto-mkdir
if (tmpBackupDir != "." && tmpBackupDir != m_defaultState.BackupDir)
{ {
state.NamingType = tmpNamingType; try
}
string tmpScript = ResolveString("AutoBackupScript",
this.m_defaultState.Script, config, regionConfig);
if (state == null && tmpScript != this.m_defaultState.Script)
{
state = new AutoBackupModuleState();
}
if (state != null)
{
state.Script = tmpScript;
}
string tmpBackupDir = ResolveString("AutoBackupDir", ".", config, regionConfig);
if (state == null && tmpBackupDir != this.m_defaultState.BackupDir)
{
state = new AutoBackupModuleState();
}
if (state != null)
{
state.BackupDir = tmpBackupDir;
// Let's give the user some convenience and auto-mkdir
if (state.BackupDir != ".")
{ {
try DirectoryInfo dirinfo = new DirectoryInfo(state.BackupDir);
if (!dirinfo.Exists)
{ {
DirectoryInfo dirinfo = new DirectoryInfo(state.BackupDir); dirinfo.Create();
if (!dirinfo.Exists)
{
dirinfo.Create();
}
}
catch (Exception e)
{
m_log.Warn(
"[AUTO BACKUP]: BAD NEWS. You won't be able to save backups to directory " +
state.BackupDir +
" because it doesn't exist or there's a permissions issue with it. Here's the exception.",
e);
} }
} }
catch (Exception e)
{
m_log.Warn(
"[AUTO BACKUP]: BAD NEWS. You won't be able to save backups to directory " +
state.BackupDir +
" because it doesn't exist or there's a permissions issue with it:",
e);
}
} }
state.BackupDir = tmpBackupDir;
if(state == null)
return m_defaultState;
return state; return state;
} }
@ -583,66 +461,6 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
} }
} }
/// <summary>
/// Helper function for ParseConfig.
/// </summary>
/// <param name="settingName"></param>
/// <param name="defaultValue"></param>
/// <param name="global"></param>
/// <param name="local"></param>
/// <returns></returns>
private double ResolveDouble(string settingName, double defaultValue, IConfig global, IConfig local)
{
if (local != null)
{
return local.GetDouble(settingName, global.GetDouble(settingName, defaultValue));
}
else
{
return global.GetDouble(settingName, defaultValue);
}
}
/// <summary>
/// Helper function for ParseConfig.
/// </summary>
/// <param name="settingName"></param>
/// <param name="defaultValue"></param>
/// <param name="global"></param>
/// <param name="local"></param>
/// <returns></returns>
private int ResolveInt(string settingName, int defaultValue, IConfig global, IConfig local)
{
if (local != null)
{
return local.GetInt(settingName, global.GetInt(settingName, defaultValue));
}
else
{
return global.GetInt(settingName, defaultValue);
}
}
/// <summary>
/// Helper function for ParseConfig.
/// </summary>
/// <param name="settingName"></param>
/// <param name="defaultValue"></param>
/// <param name="global"></param>
/// <param name="local"></param>
/// <returns></returns>
private string ResolveString(string settingName, string defaultValue, IConfig global, IConfig local)
{
if (local != null)
{
return local.GetString(settingName, global.GetString(settingName, defaultValue));
}
else
{
return global.GetString(settingName, defaultValue);
}
}
/// <summary> /// <summary>
/// Called when any auto-backup timer expires. This starts the code path for actually performing a backup. /// Called when any auto-backup timer expires. This starts the code path for actually performing a backup.
/// </summary> /// </summary>
@ -650,62 +468,21 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
/// <param name="e"></param> /// <param name="e"></param>
private void HandleElapsed(object sender, ElapsedEventArgs e) private void HandleElapsed(object sender, ElapsedEventArgs e)
{ {
// TODO: heuristic thresholds are per-region, so we should probably run heuristics once per region if (!m_enabled || m_busy)
// XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to
// check whether the region is too busy! Especially on sims with LOTS of regions.
// Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible,
// but would allow us to be semantically correct while being easier on perf.
// Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi...
// Alternative 3: Don't support per-region heuristics at all; just accept them as a global only parameter.
// Since this is pretty experimental, I haven't decided which alternative makes the most sense.
if (this.m_closed)
{
return; return;
}
bool heuristicsRun = false; m_busy = true;
bool heuristicsPassed = false; foreach (IScene scene in m_Scenes)
if (!this.m_timerMap.ContainsKey((Timer) sender))
{ {
m_log.Debug("[AUTO BACKUP]: Code-up error: timerMap doesn't contain timer " + sender); if (!m_enabled)
return;
DoRegionBackup(scene);
} }
List<IScene> tmap = this.m_timerMap[(Timer) sender]; if (m_enabled)
if (tmap != null && tmap.Count > 0)
{ {
foreach (IScene scene in tmap) m_masterTimer.Start();
{ m_busy = false;
AutoBackupModuleState state = this.m_states[scene];
bool heuristics = state.BusyCheck;
// Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region.
if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics)
{
this.DoRegionBackup(scene);
// Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off!
}
else if (heuristicsRun)
{
m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " +
scene.RegionInfo.RegionName + " right now.");
continue;
// Logical Deduction: heuristics are on but haven't been run
}
else
{
heuristicsPassed = this.RunHeuristics(scene);
heuristicsRun = true;
if (!heuristicsPassed)
{
m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " +
scene.RegionInfo.RegionName + " right now.");
continue;
}
this.DoRegionBackup(scene);
}
// Remove Old Backups
this.RemoveOldFiles(state);
}
} }
} }
@ -723,8 +500,19 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
return; return;
} }
AutoBackupModuleState state = this.m_states[scene]; m_busy = true;
AutoBackupModuleState state;
if(!m_states.TryGetValue(scene, out state))
return;
if(state == null || !state.Enabled)
return;
IRegionArchiverModule iram = scene.RequestModuleInterface<IRegionArchiverModule>(); IRegionArchiverModule iram = scene.RequestModuleInterface<IRegionArchiverModule>();
if(iram == null)
return;
string savePath = BuildOarPath(scene.RegionInfo.RegionName, string savePath = BuildOarPath(scene.RegionInfo.RegionName,
state.BackupDir, state.BackupDir,
state.NamingType); state.NamingType);
@ -733,11 +521,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
m_log.Warn("[AUTO BACKUP]: savePath is null in HandleElapsed"); m_log.Warn("[AUTO BACKUP]: savePath is null in HandleElapsed");
return; return;
} }
Guid guid = Guid.NewGuid();
m_pendingSaves.Add(guid, scene);
state.LiveRequests.Add(guid, savePath);
((Scene) scene).EventManager.OnOarFileSaved += new EventManager.OarFileSaved(EventManager_OnOarFileSaved);
Guid guid = Guid.NewGuid();
m_log.Info("[AUTO BACKUP]: Backing up region " + scene.RegionInfo.RegionName); m_log.Info("[AUTO BACKUP]: Backing up region " + scene.RegionInfo.RegionName);
// Must pass options, even if dictionary is empty! // Must pass options, even if dictionary is empty!
@ -747,6 +532,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
options["noassets"] = true; options["noassets"] = true;
iram.ArchiveRegion(savePath, guid, options); iram.ArchiveRegion(savePath, guid, options);
ExecuteScript(state.Script, savePath);
} }
// For the given state, remove backup files older than the states KeepFilesForDays property // For the given state, remove backup files older than the states KeepFilesForDays property
@ -774,23 +560,6 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
} }
} }
/// <summary>
/// Called by the Event Manager when the OnOarFileSaved event is fired.
/// </summary>
/// <param name="guid"></param>
/// <param name="message"></param>
void EventManager_OnOarFileSaved(Guid guid, string message)
{
// Ignore if the OAR save is being done by some other part of the system
if (m_pendingSaves.ContainsKey(guid))
{
AutoBackupModuleState abms = m_states[(m_pendingSaves[guid])];
ExecuteScript(abms.Script, abms.LiveRequests[guid]);
m_pendingSaves.Remove(guid);
abms.LiveRequests.Remove(guid);
}
}
/// <summary>This format may turn out to be too unwieldy to keep... /// <summary>This format may turn out to be too unwieldy to keep...
/// Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? /// Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID?
/// Sequential numbers, right? We support those, too!</summary> /// Sequential numbers, right? We support those, too!</summary>
@ -817,63 +586,6 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
return output; return output;
} }
/// <summary>Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error.</summary>
private bool RunHeuristics(IScene region)
{
try
{
return this.RunTimeDilationHeuristic(region) && this.RunAgentLimitHeuristic(region);
}
catch (Exception e)
{
m_log.Warn("[AUTO BACKUP]: Exception in RunHeuristics", e);
return false;
}
}
/// <summary>
/// If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5),
/// then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR).
/// AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy".
/// </summary>
/// <param name="region"></param>
/// <returns>Returns true if we're not too busy; false means we've got worse time dilation than the threshold.</returns>
private bool RunTimeDilationHeuristic(IScene region)
{
string regionName = region.RegionInfo.RegionName;
return region.TimeDilation >=
this.m_configSource.Configs["AutoBackupModule"].GetFloat(
regionName + ".AutoBackupDilationThreshold", 0.5f);
}
/// <summary>
/// If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10),
/// then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR).
/// AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy".
/// </summary>
/// <param name="region"></param>
/// <returns>Returns true if we're not too busy; false means we've got more agents on the sim than the threshold.</returns>
private bool RunAgentLimitHeuristic(IScene region)
{
string regionName = region.RegionInfo.RegionName;
try
{
Scene scene = (Scene) region;
// TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful...
return scene.GetRootAgentCount() <=
this.m_configSource.Configs["AutoBackupModule"].GetInt(
regionName + ".AutoBackupAgentThreshold", 10);
}
catch (InvalidCastException ice)
{
m_log.Debug(
"[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!",
ice);
return true;
// Non-obstructionist safest answer...
}
}
/// <summary> /// <summary>
/// Run the script or executable specified by the "AutoBackupScript" config setting. /// Run the script or executable specified by the "AutoBackupScript" config setting.
/// Of course this is a security risk if you let anyone modify OpenSim.ini and they want to run some nasty bash script. /// Of course this is a security risk if you let anyone modify OpenSim.ini and they want to run some nasty bash script.
@ -919,18 +631,6 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
" is yacking on stderr: " + e.Data); " is yacking on stderr: " + e.Data);
} }
/// <summary>
/// Quickly stop all timers from firing.
/// </summary>
private void StopAllTimers()
{
foreach (Timer t in this.m_timerMap.Keys)
{
t.Close();
}
this.m_closed = true;
}
/// <summary> /// <summary>
/// Determine the next unique filename by number, for "Sequential" AutoBackupNamingType. /// Determine the next unique filename by number, for "Sequential" AutoBackupNamingType.
/// </summary> /// </summary>
@ -1033,5 +733,3 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
} }
} }
} }

View File

@ -38,26 +38,24 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
/// ///
public class AutoBackupModuleState public class AutoBackupModuleState
{ {
private Dictionary<Guid, string> m_liveRequests = null;
public AutoBackupModuleState() public AutoBackupModuleState()
{ {
this.Enabled = false; Enabled = false;
this.BackupDir = "."; BackupDir = ".";
this.BusyCheck = true; SkipAssets = false;
this.SkipAssets = false; NamingType = NamingType.Time;
this.Timer = null; Script = null;
this.NamingType = NamingType.Time; KeepFilesForDays = 0;
this.Script = null;
this.KeepFilesForDays = 0;
} }
public Dictionary<Guid, string> LiveRequests public AutoBackupModuleState(AutoBackupModuleState copyFrom)
{ {
get { Enabled = copyFrom.Enabled;
return this.m_liveRequests ?? BackupDir = copyFrom.BackupDir;
(this.m_liveRequests = new Dictionary<Guid, string>(1)); SkipAssets = copyFrom.SkipAssets;
} NamingType = copyFrom.NamingType;
Script = copyFrom.Script;
KeepFilesForDays = copyFrom.KeepFilesForDays;
} }
public bool Enabled public bool Enabled
@ -66,33 +64,6 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
set; set;
} }
public System.Timers.Timer Timer
{
get;
set;
}
public double IntervalMinutes
{
get
{
if (this.Timer == null)
{
return -1.0;
}
else
{
return this.Timer.Interval / 60000.0;
}
}
}
public bool BusyCheck
{
get;
set;
}
public bool SkipAssets public bool SkipAssets
{ {
get; get;
@ -126,10 +97,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
public new string ToString() public new string ToString()
{ {
string retval = ""; string retval = "";
retval += "[AUTO BACKUP]: AutoBackup: " + (Enabled ? "ENABLED" : "DISABLED") + "\n"; retval += "[AUTO BACKUP]: AutoBackup: " + (Enabled ? "ENABLED" : "DISABLED") + "\n";
retval += "[AUTO BACKUP]: Interval: " + IntervalMinutes + " minutes" + "\n";
retval += "[AUTO BACKUP]: Do Busy Check: " + (BusyCheck ? "Yes" : "No") + "\n";
retval += "[AUTO BACKUP]: Naming Type: " + NamingType.ToString() + "\n"; retval += "[AUTO BACKUP]: Naming Type: " + NamingType.ToString() + "\n";
retval += "[AUTO BACKUP]: Backup Dir: " + BackupDir + "\n"; retval += "[AUTO BACKUP]: Backup Dir: " + BackupDir + "\n";
retval += "[AUTO BACKUP]: Script: " + Script + "\n"; retval += "[AUTO BACKUP]: Script: " + Script + "\n";
@ -137,4 +105,3 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup
} }
} }
} }