aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs1509
1 files changed, 745 insertions, 764 deletions
diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs
index 364697b..a4dbea4 100644
--- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs
+++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs
@@ -1,764 +1,745 @@
1/* 1#pragma warning disable 1587
2 * Copyright (c) Contributors, http://opensimulator.org/ 2///
3 * See CONTRIBUTORS.TXT for a full list of copyright holders. 3/// Copyright (c) Contributors, http://opensimulator.org/
4 * 4/// See CONTRIBUTORS.TXT for a full list of copyright holders.
5 * Redistribution and use in source and binary forms, with or without 5///
6 * modification, are permitted provided that the following conditions are met: 6/// Redistribution and use in source and binary forms, with or without
7 * * Redistributions of source code must retain the above copyright 7/// modification, are permitted provided that the following conditions are met:
8 * notice, this list of conditions and the following disclaimer. 8/// * Redistributions of source code must retain the above copyright
9 * * Redistributions in binary form must reproduce the above copyright 9/// notice, this list of conditions and the following disclaimer.
10 * notice, this list of conditions and the following disclaimer in the 10/// * Redistributions in binary form must reproduce the above copyright
11 * documentation and/or other materials provided with the distribution. 11/// notice, this list of conditions and the following disclaimer in the
12 * * Neither the name of the OpenSimulator Project nor the 12/// documentation and/or other materials provided with the distribution.
13 * names of its contributors may be used to endorse or promote products 13/// * Neither the name of the OpenSimulator Project nor the
14 * derived from this software without specific prior written permission. 14/// names of its contributors may be used to endorse or promote products
15 * 15/// derived from this software without specific prior written permission.
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY 16///
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17/// THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18/// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY 19/// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20/// DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21/// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22/// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23/// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24/// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25/// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 */ 26/// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 27///
28using System; 28
29using System.IO; 29using System;
30using System.Timers; 30using System.Collections.Generic;
31using System.Diagnostics; 31using System.Diagnostics;
32using System.Reflection; 32using System.Globalization;
33using System.Collections.Generic; 33using System.IO;
34using log4net; 34using System.Reflection;
35using Nini; 35using System.Timers;
36using Nini.Config; 36using System.Text.RegularExpressions;
37using OpenSim.Framework; 37using log4net;
38using OpenSim.Framework.Statistics; 38using Nini.Config;
39using OpenSim.Region.Framework.Interfaces; 39using OpenSim.Framework;
40using OpenSim.Region.Framework.Scenes; 40using OpenSim.Region.Framework.Interfaces;
41 41using OpenSim.Region.Framework.Scenes;
42 42
43/* 43///
44 * Config Settings Documentation. 44/// Config Settings Documentation.
45 * At the TOP LEVEL, e.g. in OpenSim.ini, we have the following options: 45/// At the TOP LEVEL, e.g. in OpenSim.ini, we have the following options:
46 * In the [Modules] section: 46/// EACH REGION, in OpenSim.ini, can have the following settings under the [AutoBackupModule] section.
47 * AutoBackupModule: True/False. Default: False. If True, use the auto backup module. Otherwise it will be disabled regardless of what settings are in Regions.ini! 47/// IMPORTANT: You may optionally specify the key name as follows for a per-region key: <Region Name>.<Key Name>
48 * EACH REGION, in OpenSim.ini, can have the following settings under the [AutoBackupModule] section. 48/// Example: My region is named Foo.
49 * VERY IMPORTANT: You must create the key name as follows: <Region Name>.<Key Name> 49/// If I wanted to specify the "AutoBackupInterval" key just for this region, I would name my key "Foo.AutoBackupInterval", under the [AutoBackupModule] section of OpenSim.ini.
50 * Example: My region is named Foo. 50/// Instead of specifying them on a per-region basis, you can also omit the region name to specify the default setting for all regions.
51 * If I wanted to specify the "AutoBackupInterval" key below, I would name my key "Foo.AutoBackupInterval", under the [AutoBackupModule] section of OpenSim.ini. 51/// Region-specific settings take precedence.
52 * Instead of specifying them on a per-region basis, you can also omit the region name to specify the default setting for all regions. 52///
53 * Region-specific settings take precedence. 53/// AutoBackupModuleEnabled: True/False. Default: False. If True, use the auto backup module. This setting does not support per-region basis.
54 * AutoBackup: True/False. Default: False. If True, activate auto backup functionality. 54/// All other settings under [AutoBackupModule] are ignored if AutoBackupModuleEnabled is false, even per-region settings!
55 * This is the only required option for enabling auto-backup; the other options have sane defaults. 55/// AutoBackup: True/False. Default: False. If True, activate auto backup functionality.
56 * If False, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored. 56/// This is the only required option for enabling auto-backup; the other options have sane defaults.
57 * AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours). 57/// If False for a particular region, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored.
58 * The number of minutes between each backup attempt. 58/// If False globally (the default), only regions that specifically override this with "FooRegion.AutoBackup = true" will get AutoBackup functionality.
59 * If a negative or zero value is given, it is equivalent to setting AutoBackup = False. 59/// AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours).
60 * AutoBackupBusyCheck: True/False. Default: True. 60/// The number of minutes between each backup attempt.
61 * If True, we will only take an auto-backup if a set of conditions are met. 61/// If a negative or zero value is given, it is equivalent to setting AutoBackup = False.
62 * These conditions are heuristics to try and avoid taking a backup when the sim is busy. 62/// AutoBackupBusyCheck: True/False. Default: True.
63 * AutoBackupScript: String. Default: not specified (disabled). 63/// If True, we will only take an auto-backup if a set of conditions are met.
64 * File path to an executable script or binary to run when an automatic backup is taken. 64/// These conditions are heuristics to try and avoid taking a backup when the sim is busy.
65 * The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary. 65/// AutoBackupScript: String. Default: not specified (disabled).
66 * Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results! 66/// File path to an executable script or binary to run when an automatic backup is taken.
67 * argv[1] of the executed file/script will be the file name of the generated OAR. 67/// The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary.
68 * If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console. 68/// Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results!
69 * AutoBackupNaming: string. Default: Time. 69/// argv[1] of the executed file/script will be the file name of the generated OAR.
70 * One of three strings (case insensitive): 70/// If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console.
71 * "Time": Current timestamp is appended to file name. An existing file will never be overwritten. 71/// AutoBackupNaming: string. Default: Time.
72 * "Sequential": A number is appended to the file name. So if RegionName_x.oar exists, we'll save to RegionName_{x+1}.oar next. An existing file will never be overwritten. 72/// One of three strings (case insensitive):
73 * "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file. 73/// "Time": Current timestamp is appended to file name. An existing file will never be overwritten.
74 * AutoBackupDir: String. Default: "." (the current directory). 74/// "Sequential": A number is appended to the file name. So if RegionName_x.oar exists, we'll save to RegionName_{x+1}.oar next. An existing file will never be overwritten.
75 * A directory (absolute or relative) where backups should be saved. 75/// "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file.
76 * AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass. 76/// AutoBackupDir: String. Default: "." (the current directory).
77 * If the time dilation is below this value, don't take a backup right now. 77/// A directory (absolute or relative) where backups should be saved.
78 * AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass. 78/// AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass.
79 * If the number of agents is greater than this value, don't take a backup right now. 79/// If the time dilation is below this value, don't take a backup right now.
80 * */ 80/// AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass.
81 81/// If the number of agents is greater than this value, don't take a backup right now.
82namespace OpenSim.Region.OptionalModules.World.AutoBackup 82///
83{ 83
84 84namespace OpenSim.Region.OptionalModules.World.AutoBackup
85 public enum NamingType 85{
86 { 86 public enum NamingType
87 TIME, 87 {
88 SEQUENTIAL, 88 Time,
89 OVERWRITE 89 Sequential,
90 } 90 Overwrite
91 91 }
92 public class AutoBackupModule : ISharedRegionModule, IRegionModuleBase 92
93 { 93 public class AutoBackupModule : ISharedRegionModule
94 94 {
95 private static readonly ILog m_log = LogManager.GetLogger (MethodBase.GetCurrentMethod ().DeclaringType); 95 private static readonly ILog m_log =
96 96 LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
97 //AutoBackupModuleState: Auto-Backup state for one region (scene). 97
98 public class AutoBackupModuleState 98 /// True means IRegionModuleBase.Close() was called on us, and we should stop operation ASAP.
99 { 99 /// Used to prevent elapsing timers after Close() is called from trying to start an autobackup while the sim is shutting down.
100 private bool m_enabled = false; 100 private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState();
101 private NamingType m_naming = NamingType.TIME; 101
102 private Timer m_timer = null; 102 /// Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions.
103 private bool m_busycheck = true; 103 /// Also helps if you don't want AutoBackup at all
104 private string m_script = null; 104 private readonly Dictionary<IScene, AutoBackupModuleState> m_states =
105 private string m_dir = "."; 105 new Dictionary<IScene, AutoBackupModuleState>(1);
106 106
107 public AutoBackupModuleState () 107 private readonly Dictionary<Timer, List<IScene>> m_timerMap =
108 { 108 new Dictionary<Timer, List<IScene>>(1);
109 109
110 } 110 private readonly Dictionary<double, Timer> m_timers = new Dictionary<double, Timer>(1);
111 111
112 public void SetEnabled (bool b) 112 private bool m_enabled;
113 { 113
114 m_enabled = b; 114 /// Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState!
115 } 115 private bool m_closed;
116 116
117 public bool GetEnabled () 117 private IConfigSource m_configSource;
118 { 118
119 return m_enabled; 119 public bool IsSharedModule
120 } 120 {
121 121 get { return true; }
122 public Timer GetTimer () 122 }
123 { 123
124 return m_timer; 124 #region ISharedRegionModule Members
125 } 125
126 126 string IRegionModuleBase.Name
127 public double GetIntervalMinutes () 127 {
128 { 128 get { return "AutoBackupModule"; }
129 if(m_timer == null) 129 }
130 { 130
131 return -1.0; 131 Type IRegionModuleBase.ReplaceableInterface
132 } 132 {
133 else 133 get { return null; }
134 { 134 }
135 return m_timer.Interval / 60000.0; 135
136 } 136 void IRegionModuleBase.Initialise(IConfigSource source)
137 } 137 {
138 138 /// Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module
139 public void SetTimer (Timer t) 139 this.m_configSource = source;
140 { 140 IConfig moduleConfig = source.Configs["AutoBackupModule"];
141 m_timer = t; 141 if (moduleConfig == null)
142 } 142 {
143 143 this.m_enabled = false;
144 public bool GetBusyCheck () 144 return;
145 { 145 }
146 return m_busycheck; 146 else
147 } 147 {
148 148 this.m_enabled = moduleConfig.GetBoolean("AutoBackupModuleEnabled", false);
149 public void SetBusyCheck (bool b) 149 if (this.m_enabled)
150 { 150 {
151 m_busycheck = b; 151 m_log.Info("[AUTO BACKUP]: AutoBackupModule enabled");
152 } 152 }
153 153 else
154 154 {
155 public string GetScript () 155 return;
156 { 156 }
157 return m_script; 157 }
158 } 158
159 159 Timer defTimer = new Timer(43200000);
160 public void SetScript (string s) 160 this.m_defaultState.Timer = defTimer;
161 { 161 this.m_timers.Add(43200000, defTimer);
162 m_script = s; 162 defTimer.Elapsed += this.HandleElapsed;
163 } 163 defTimer.AutoReset = true;
164 164 defTimer.Start();
165 public string GetBackupDir () 165
166 { 166 AutoBackupModuleState abms = this.ParseConfig(null, true);
167 return m_dir; 167 m_log.Debug("[AUTO BACKUP]: Here is the default config:");
168 } 168 m_log.Debug(abms.ToString());
169 169 }
170 public void SetBackupDir (string s) 170
171 { 171 void IRegionModuleBase.Close()
172 m_dir = s; 172 {
173 } 173 if (!this.m_enabled)
174 174 {
175 public NamingType GetNamingType () 175 return;
176 { 176 }
177 return m_naming; 177
178 } 178 /// We don't want any timers firing while the sim's coming down; strange things may happen.
179 179 this.StopAllTimers();
180 public void SetNamingType (NamingType n) 180 }
181 { 181
182 m_naming = n; 182 void IRegionModuleBase.AddRegion(Scene scene)
183 } 183 {
184 184 /// NO-OP. Wait for the region to be loaded.
185 public string ToString() 185 }
186 { 186
187 string retval = ""; 187 void IRegionModuleBase.RemoveRegion(Scene scene)
188 188 {
189 retval += "[AUTO BACKUP]: AutoBackup: " + (GetEnabled() ? "ENABLED" : "DISABLED") + "\n"; 189 if (!this.m_enabled)
190 retval += "[AUTO BACKUP]: Interval: " + GetIntervalMinutes() + " minutes" + "\n"; 190 {
191 retval += "[AUTO BACKUP]: Do Busy Check: " + (GetBusyCheck() ? "Yes" : "No") + "\n"; 191 return;
192 retval += "[AUTO BACKUP]: Naming Type: " + GetNamingType().ToString() + "\n"; 192 }
193 retval += "[AUTO BACKUP]: Backup Dir: " + GetBackupDir() + "\n"; 193
194 retval += "[AUTO BACKUP]: Script: " + GetScript() + "\n"; 194 if (this.m_states.ContainsKey(scene))
195 return retval; 195 {
196 } 196 AutoBackupModuleState abms = this.m_states[scene];
197 } 197
198 198 /// Remove this scene out of the timer map list
199 //Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. 199 Timer timer = abms.Timer;
200 //Also helps if you don't want AutoBackup at all 200 List<IScene> list = this.m_timerMap[timer];
201 readonly Dictionary<IScene, AutoBackupModuleState> states = new Dictionary<IScene, AutoBackupModuleState> (4); 201 list.Remove(scene);
202 readonly Dictionary<double, Timer> timers = new Dictionary<double, Timer> (1); 202
203 readonly Dictionary<Timer, List<IScene>> timerMap = new Dictionary<Timer, List<IScene>> (1); 203 /// Shut down the timer if this was the last scene for the timer
204 private IConfigSource m_configSource = null; 204 if (list.Count == 0)
205 private bool m_Enabled = false; 205 {
206 //Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! 206 this.m_timerMap.Remove(timer);
207 private bool m_closed = false; 207 this.m_timers.Remove(timer.Interval);
208 //True means IRegionModuleBase.Close() was called on us, and we should stop operation ASAP. 208 timer.Close();
209 //Used to prevent elapsing timers after Close() is called from trying to start an autobackup while the sim is shutting down. 209 }
210 readonly AutoBackupModuleState defaultState = new AutoBackupModuleState(); 210 this.m_states.Remove(scene);
211 211 }
212 public AutoBackupModule () 212 }
213 { 213
214 214 void IRegionModuleBase.RegionLoaded(Scene scene)
215 } 215 {
216 216 if (!this.m_enabled)
217 #region IRegionModuleBase implementation 217 {
218 void IRegionModuleBase.Initialise (Nini.Config.IConfigSource source) 218 return;
219 { 219 }
220 //Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module 220
221 m_configSource = source; 221 /// This really ought not to happen, but just in case, let's pretend it didn't...
222 IConfig moduleConfig = source.Configs["Modules"]; 222 if (scene == null)
223 if (moduleConfig != null) { 223 {
224 m_Enabled = moduleConfig.GetBoolean ("AutoBackupModule", false); 224 return;
225 if (m_Enabled) { 225 }
226 m_log.Info ("[AUTO BACKUP]: AutoBackupModule enabled"); 226
227 } 227 AutoBackupModuleState abms = this.ParseConfig(scene, false);
228 else { 228 m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName);
229 m_log.Info ("[AUTO BACKUP]: AutoBackupModule disabled"); 229 m_log.Debug((abms == null ? "DEFAULT" : abms.ToString()));
230 return; 230 }
231 } 231
232 } 232 void ISharedRegionModule.PostInitialise()
233 233 {
234 Timer defTimer = new Timer(720 * 60000); 234 /// I don't care right now.
235 defaultState.SetTimer(defTimer); 235 }
236 timers.Add (720*60000, defTimer); 236
237 defTimer.Elapsed += HandleElapsed; 237 #endregion
238 defTimer.AutoReset = true; 238
239 defTimer.Start (); 239 private AutoBackupModuleState ParseConfig(IScene scene, bool parseDefault)
240 240 {
241 AutoBackupModuleState abms = ParseConfig(null, true); 241 string sRegionName;
242 m_log.Debug("[AUTO BACKUP]: Config for default"); 242 string sRegionLabel;
243 m_log.Debug(abms.ToString()); 243 string prepend;
244 } 244 AutoBackupModuleState state;
245 245
246 void IRegionModuleBase.Close () 246 if (parseDefault)
247 { 247 {
248 if (!m_Enabled) 248 sRegionName = null;
249 return; 249 sRegionLabel = "DEFAULT";
250 250 prepend = "";
251 //We don't want any timers firing while the sim's coming down; strange things may happen. 251 state = this.m_defaultState;
252 StopAllTimers (); 252 }
253 } 253 else
254 254 {
255 void IRegionModuleBase.AddRegion (Framework.Scenes.Scene scene) 255 sRegionName = scene.RegionInfo.RegionName;
256 { 256 sRegionLabel = sRegionName;
257 //NO-OP. Wait for the region to be loaded. 257 prepend = sRegionName + ".";
258 } 258 state = null;
259 259 }
260 void IRegionModuleBase.RemoveRegion (Framework.Scenes.Scene scene) 260
261 { 261 /// Read the config settings and set variables.
262 if (!m_Enabled) 262 IConfig config = this.m_configSource.Configs["AutoBackupModule"];
263 return; 263 if (config == null)
264 264 {
265 if(states.ContainsKey(scene)) 265 /// defaultState would be disabled too if the section doesn't exist.
266 { 266 state = this.m_defaultState;
267 AutoBackupModuleState abms = states[scene]; 267 m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled.");
268 268 return state;
269 //Remove this scene out of the timer map list 269 }
270 Timer timer = abms.GetTimer (); 270
271 List<IScene> list = timerMap[timer]; 271 bool tmpEnabled = config.GetBoolean(prepend + "AutoBackup", this.m_defaultState.Enabled);
272 list.Remove (scene); 272 if (state == null && tmpEnabled != this.m_defaultState.Enabled)
273 273 //Varies from default state
274 //Shut down the timer if this was the last scene for the timer 274 {
275 if (list.Count == 0) { 275 state = new AutoBackupModuleState();
276 timerMap.Remove (timer); 276 }
277 timers.Remove (timer.Interval); 277
278 timer.Close (); 278 if (state != null)
279 } 279 {
280 states.Remove(scene); 280 state.Enabled = tmpEnabled;
281 } 281 }
282 } 282
283 283 /// If you don't want AutoBackup, we stop.
284 void IRegionModuleBase.RegionLoaded (Framework.Scenes.Scene scene) 284 if ((state == null && !this.m_defaultState.Enabled) || (state != null && !state.Enabled))
285 { 285 {
286 if (!m_Enabled) 286 m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled.");
287 return; 287 return state;
288 288 }
289 //This really ought not to happen, but just in case, let's pretend it didn't... 289 else
290 if (scene == null) 290 {
291 return; 291 m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED.");
292 292 }
293 AutoBackupModuleState abms = ParseConfig(scene, false); 293
294 m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName); 294 /// Borrow an existing timer if one exists for the same interval; otherwise, make a new one.
295 m_log.Debug((abms == null ? "DEFAULT" : abms.ToString())); 295 double interval =
296 } 296 config.GetDouble(prepend + "AutoBackupInterval", this.m_defaultState.IntervalMinutes)*
297 297 60000.0;
298 AutoBackupModuleState ParseConfig (IScene scene, bool parseDefault) 298 if (state == null && interval != this.m_defaultState.IntervalMinutes*60000.0)
299 { 299 {
300 string sRegionName; 300 state = new AutoBackupModuleState();
301 string sRegionLabel; 301 }
302 string prepend; 302
303 AutoBackupModuleState state; 303 if (this.m_timers.ContainsKey(interval))
304 304 {
305 if(parseDefault) 305 if (state != null)
306 { 306 {
307 sRegionName = null; 307 state.Timer = this.m_timers[interval];
308 sRegionLabel = "DEFAULT"; 308 }
309 prepend = ""; 309 m_log.Debug("[AUTO BACKUP]: Reusing timer for " + interval + " msec for region " +
310 state = defaultState; 310 sRegionLabel);
311 } 311 }
312 else 312 else
313 { 313 {
314 sRegionName = scene.RegionInfo.RegionName; 314 /// 0 or negative interval == do nothing.
315 sRegionLabel = sRegionName; 315 if (interval <= 0.0 && state != null)
316 prepend = sRegionName + "."; 316 {
317 state = null; 317 state.Enabled = false;
318 } 318 return state;
319 319 }
320 //Read the config settings and set variables. 320 if (state == null)
321 IConfig config = m_configSource.Configs["AutoBackupModule"]; 321 {
322 if (config == null) { 322 state = new AutoBackupModuleState();
323 state = defaultState; //defaultState would be disabled too if the section doesn't exist. 323 }
324 m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); 324 Timer tim = new Timer(interval);
325 return state; 325 state.Timer = tim;
326 } 326 //Milliseconds -> minutes
327 327 this.m_timers.Add(interval, tim);
328 bool tmpEnabled = config.GetBoolean (prepend + "AutoBackup", defaultState.GetEnabled()); 328 tim.Elapsed += this.HandleElapsed;
329 if(state == null && tmpEnabled != defaultState.GetEnabled()) //Varies from default state 329 tim.AutoReset = true;
330 { 330 tim.Start();
331 state = new AutoBackupModuleState(); 331 }
332 } 332
333 333 /// Add the current region to the list of regions tied to this timer.
334 if(state != null) 334 if (scene != null)
335 { 335 {
336 state.SetEnabled (tmpEnabled); 336 if (state != null)
337 } 337 {
338 338 if (this.m_timerMap.ContainsKey(state.Timer))
339 //If you don't want AutoBackup, we stop. 339 {
340 if ((state == null && !defaultState.GetEnabled()) || (state != null && !state.GetEnabled ())) { 340 this.m_timerMap[state.Timer].Add(scene);
341 m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); 341 }
342 return state; 342 else
343 } else { 343 {
344 m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED."); 344 List<IScene> scns = new List<IScene>(1);
345 } 345 scns.Add(scene);
346 346 this.m_timerMap.Add(state.Timer, scns);
347 //Borrow an existing timer if one exists for the same interval; otherwise, make a new one. 347 }
348 double interval = config.GetDouble (prepend + "AutoBackupInterval", defaultState.GetIntervalMinutes()) * 60000.0; 348 }
349 if(state == null && interval != defaultState.GetIntervalMinutes() * 60000.0) 349 else
350 { 350 {
351 state = new AutoBackupModuleState(); 351 if (this.m_timerMap.ContainsKey(this.m_defaultState.Timer))
352 } 352 {
353 353 this.m_timerMap[this.m_defaultState.Timer].Add(scene);
354 if (timers.ContainsKey (interval)) { 354 }
355 if(state != null) 355 else
356 state.SetTimer (timers[interval]); 356 {
357 m_log.Debug ("[AUTO BACKUP]: Reusing timer for " + interval + " msec for region " + sRegionLabel); 357 List<IScene> scns = new List<IScene>(1);
358 } else { 358 scns.Add(scene);
359 //0 or negative interval == do nothing. 359 this.m_timerMap.Add(this.m_defaultState.Timer, scns);
360 if (interval <= 0.0 && state != null) { 360 }
361 state.SetEnabled (false); 361 }
362 return state; 362 }
363 } 363
364 if(state == null) 364 bool tmpBusyCheck = config.GetBoolean(prepend + "AutoBackupBusyCheck",
365 state = new AutoBackupModuleState(); 365 this.m_defaultState.BusyCheck);
366 Timer tim = new Timer (interval); 366 if (state == null && tmpBusyCheck != this.m_defaultState.BusyCheck)
367 state.SetTimer (tim); 367 {
368 //Milliseconds -> minutes 368 state = new AutoBackupModuleState();
369 timers.Add (interval, tim); 369 }
370 tim.Elapsed += HandleElapsed; 370
371 tim.AutoReset = true; 371 if (state != null)
372 tim.Start (); 372 {
373 //m_log.Debug("[AUTO BACKUP]: New timer for " + interval + " msec for region " + sRegionName); 373 state.BusyCheck = tmpBusyCheck;
374 } 374 }
375 375
376 //Add the current region to the list of regions tied to this timer. 376 /// Set file naming algorithm
377 if(scene != null) 377 string stmpNamingType = config.GetString(prepend + "AutoBackupNaming",
378 { 378 this.m_defaultState.NamingType.ToString());
379 if(state != null) 379 NamingType tmpNamingType;
380 { 380 if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase))
381 if (timerMap.ContainsKey (state.GetTimer ())) { 381 {
382 timerMap[state.GetTimer ()].Add (scene); 382 tmpNamingType = NamingType.Time;
383 } else { 383 }
384 List<IScene> scns = new List<IScene> (1); 384 else if (stmpNamingType.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase))
385 scns.Add (scene); 385 {
386 timerMap.Add (state.GetTimer (), scns); 386 tmpNamingType = NamingType.Sequential;
387 } 387 }
388 } 388 else if (stmpNamingType.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase))
389 else 389 {
390 { 390 tmpNamingType = NamingType.Overwrite;
391 if(timerMap.ContainsKey(defaultState.GetTimer())) { 391 }
392 timerMap[defaultState.GetTimer()].Add(scene); 392 else
393 } else { 393 {
394 List<IScene> scns = new List<IScene> (1); 394 m_log.Warn("Unknown naming type specified for region " + sRegionLabel + ": " +
395 scns.Add(scene); 395 stmpNamingType);
396 timerMap.Add(defaultState.GetTimer(), scns); 396 tmpNamingType = NamingType.Time;
397 } 397 }
398 } 398
399 } 399 if (state == null && tmpNamingType != this.m_defaultState.NamingType)
400 400 {
401 bool tmpBusyCheck = config.GetBoolean (prepend + "AutoBackupBusyCheck", defaultState.GetBusyCheck()); 401 state = new AutoBackupModuleState();
402 if(state == null && tmpBusyCheck != defaultState.GetBusyCheck()) 402 }
403 { 403
404 state = new AutoBackupModuleState(); 404 if (state != null)
405 } 405 {
406 406 state.NamingType = tmpNamingType;
407 if(state != null) 407 }
408 { 408
409 state.SetBusyCheck (tmpBusyCheck); 409 string tmpScript = config.GetString(prepend + "AutoBackupScript",
410 } 410 this.m_defaultState.Script);
411 411 if (state == null && tmpScript != this.m_defaultState.Script)
412 //Set file naming algorithm 412 {
413 string stmpNamingType = config.GetString (prepend + "AutoBackupNaming", defaultState.GetNamingType().ToString()); 413 state = new AutoBackupModuleState();
414 NamingType tmpNamingType; 414 }
415 if (stmpNamingType.Equals ("Time", StringComparison.CurrentCultureIgnoreCase)) { 415
416 tmpNamingType = NamingType.TIME; 416 if (state != null)
417 } else if (stmpNamingType.Equals ("Sequential", StringComparison.CurrentCultureIgnoreCase)) { 417 {
418 tmpNamingType = NamingType.SEQUENTIAL; 418 state.Script = tmpScript;
419 } else if (stmpNamingType.Equals ("Overwrite", StringComparison.CurrentCultureIgnoreCase)) { 419 }
420 tmpNamingType = NamingType.OVERWRITE; 420
421 } else { 421 string tmpBackupDir = config.GetString(prepend + "AutoBackupDir", ".");
422 m_log.Warn ("Unknown naming type specified for region " + sRegionLabel + ": " + stmpNamingType); 422 if (state == null && tmpBackupDir != this.m_defaultState.BackupDir)
423 tmpNamingType = NamingType.TIME; 423 {
424 } 424 state = new AutoBackupModuleState();
425 425 }
426 if(state == null && tmpNamingType != defaultState.GetNamingType()) 426
427 { 427 if (state != null)
428 state = new AutoBackupModuleState(); 428 {
429 } 429 state.BackupDir = tmpBackupDir;
430 430 /// Let's give the user *one* convenience and auto-mkdir
431 if(state != null) 431 if (state.BackupDir != ".")
432 { 432 {
433 state.SetNamingType(tmpNamingType); 433 try
434 } 434 {
435 435 DirectoryInfo dirinfo = new DirectoryInfo(state.BackupDir);
436 string tmpScript = config.GetString (prepend + "AutoBackupScript", defaultState.GetScript()); 436 if (!dirinfo.Exists)
437 if(state == null && tmpScript != defaultState.GetScript()) 437 {
438 { 438 dirinfo.Create();
439 state = new AutoBackupModuleState(); 439 }
440 } 440 }
441 441 catch (Exception e)
442 if(state != null) 442 {
443 { 443 m_log.Warn(
444 state.SetScript (tmpScript); 444 "BAD NEWS. You won't be able to save backups to directory " +
445 } 445 state.BackupDir +
446 446 " because it doesn't exist or there's a permissions issue with it. Here's the exception.",
447 string tmpBackupDir = config.GetString (prepend + "AutoBackupDir", "."); 447 e);
448 if(state == null && tmpBackupDir != defaultState.GetBackupDir()) 448 }
449 { 449 }
450 state = new AutoBackupModuleState(); 450 }
451 } 451
452 452 return state;
453 if(state != null) 453 }
454 { 454
455 state.SetBackupDir (tmpBackupDir); 455 private void HandleElapsed(object sender, ElapsedEventArgs e)
456 //Let's give the user *one* convenience and auto-mkdir 456 {
457 if (state.GetBackupDir () != ".") { 457 /// TODO?: heuristic thresholds are per-region, so we should probably run heuristics once per region
458 try { 458 /// XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to
459 DirectoryInfo dirinfo = new DirectoryInfo (state.GetBackupDir ()); 459 /// check whether the region is too busy! Especially on sims with LOTS of regions.
460 if (!dirinfo.Exists) { 460 /// Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible,
461 dirinfo.Create (); 461 /// but would allow us to be semantically correct while being easier on perf.
462 } 462 /// Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi...
463 } catch (Exception e) { 463 if (this.m_closed)
464 m_log.Warn ("BAD NEWS. You won't be able to save backups to directory " + state.GetBackupDir () + " because it doesn't exist or there's a permissions issue with it. Here's the exception.", e); 464 {
465 } 465 return;
466 } 466 }
467 } 467 bool heuristicsRun = false;
468 468 bool heuristicsPassed = false;
469 return state; 469 if (!this.m_timerMap.ContainsKey((Timer) sender))
470 } 470 {
471 471 m_log.Debug("Code-up error: timerMap doesn't contain timer " + sender);
472 void HandleElapsed (object sender, ElapsedEventArgs e) 472 }
473 { 473
474 //TODO?: heuristic thresholds are per-region, so we should probably run heuristics once per region 474 List<IScene> tmap = this.m_timerMap[(Timer) sender];
475 //XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to 475 if (tmap != null && tmap.Count > 0)
476 //check whether the region is too busy! Especially on sims with LOTS of regions. 476 {
477 //Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible, 477 foreach (IScene scene in tmap)
478 // but would allow us to be semantically correct while being easier on perf. 478 {
479 //Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi... 479 AutoBackupModuleState state = this.m_states[scene];
480 if (m_closed) 480 bool heuristics = state.BusyCheck;
481 return; 481
482 bool heuristicsRun = false; 482 /// Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region.
483 bool heuristicsPassed = false; 483 if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics)
484 if (!timerMap.ContainsKey ((Timer)sender)) { 484 {
485 m_log.Debug ("Code-up error: timerMap doesn't contain timer " + sender.ToString ()); 485 this.DoRegionBackup(scene);
486 } 486 /// Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off!
487 487 }
488 List<IScene> tmap = timerMap[(Timer)sender]; 488 else if (heuristicsRun)
489 if(tmap != null && tmap.Count > 0) 489 {
490 foreach (IScene scene in tmap) { 490 m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " +
491 AutoBackupModuleState state = states[scene]; 491 scene.RegionInfo.RegionName + " right now.");
492 bool heuristics = state.GetBusyCheck (); 492 continue;
493 493 /// Logical Deduction: heuristics are on but haven't been run
494 //Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region. 494 }
495 if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) { 495 else
496 doRegionBackup (scene); 496 {
497 //Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! 497 heuristicsPassed = this.RunHeuristics(scene);
498 } else if (heuristics && heuristicsRun && !heuristicsPassed) { 498 heuristicsRun = true;
499 m_log.Info ("[AUTO BACKUP]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); 499 if (!heuristicsPassed)
500 continue; 500 {
501 //Logical Deduction: heuristics are on but haven't been run 501 m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " +
502 } else { 502 scene.RegionInfo.RegionName + " right now.");
503 heuristicsPassed = RunHeuristics (scene); 503 continue;
504 heuristicsRun = true; 504 }
505 if (!heuristicsPassed) { 505 this.DoRegionBackup(scene);
506 m_log.Info ("[AUTO BACKUP]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); 506 }
507 continue; 507 }
508 } 508 }
509 doRegionBackup (scene); 509 }
510 } 510
511 } 511 private void DoRegionBackup(IScene scene)
512 } 512 {
513 513 if (scene.RegionStatus != RegionStatus.Up)
514 void doRegionBackup (IScene scene) 514 {
515 { 515 /// We won't backup a region that isn't operating normally.
516 if (scene.RegionStatus != RegionStatus.Up) { 516 m_log.Warn("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName +
517 //We won't backup a region that isn't operating normally. 517 " because its status is " + scene.RegionStatus);
518 m_log.Warn ("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName + " because its status is " + scene.RegionStatus.ToString ()); 518 return;
519 return; 519 }
520 } 520
521 521 AutoBackupModuleState state = this.m_states[scene];
522 AutoBackupModuleState state = states[scene]; 522 IRegionArchiverModule iram = scene.RequestModuleInterface<IRegionArchiverModule>();
523 IRegionArchiverModule iram = scene.RequestModuleInterface<IRegionArchiverModule> (); 523 string savePath = BuildOarPath(scene.RegionInfo.RegionName,
524 string savePath = BuildOarPath (scene.RegionInfo.RegionName, state.GetBackupDir (), state.GetNamingType ()); 524 state.BackupDir,
525 //m_log.Debug("[AUTO BACKUP]: savePath = " + savePath); 525 state.NamingType);
526 if (savePath == null) { 526 /// m_log.Debug("[AUTO BACKUP]: savePath = " + savePath);
527 m_log.Warn ("[AUTO BACKUP]: savePath is null in HandleElapsed"); 527 if (savePath == null)
528 return; 528 {
529 } 529 m_log.Warn("[AUTO BACKUP]: savePath is null in HandleElapsed");
530 iram.ArchiveRegion (savePath, null); 530 return;
531 ExecuteScript (state.GetScript (), savePath); 531 }
532 } 532 iram.ArchiveRegion(savePath, Guid.NewGuid(), null);
533 533 ExecuteScript(state.Script, savePath);
534 string IRegionModuleBase.Name { 534 }
535 get { return "AutoBackupModule"; } 535
536 } 536 /// This format may turn out to be too unwieldy to keep...
537 537 /// Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID?
538 Type IRegionModuleBase.ReplaceableInterface { 538 /// Sequential numbers, right? Ugh. Almost makes TOO much sense.
539 get { return null; } 539 private static string GetTimeString()
540 } 540 {
541 541 StringWriter sw = new StringWriter();
542 #endregion 542 sw.Write("_");
543 #region ISharedRegionModule implementation 543 DateTime now = DateTime.Now;
544 void ISharedRegionModule.PostInitialise () 544 sw.Write(now.Year);
545 { 545 sw.Write("y_");
546 //I don't care right now. 546 sw.Write(now.Month);
547 } 547 sw.Write("M_");
548 548 sw.Write(now.Day);
549 #endregion 549 sw.Write("d_");
550 550 sw.Write(now.Hour);
551 //Is this even needed? 551 sw.Write("h_");
552 public bool IsSharedModule { 552 sw.Write(now.Minute);
553 get { return true; } 553 sw.Write("m_");
554 } 554 sw.Write(now.Second);
555 555 sw.Write("s");
556 private string BuildOarPath (string regionName, string baseDir, NamingType naming) 556 sw.Flush();
557 { 557 string output = sw.ToString();
558 FileInfo path = null; 558 sw.Close();
559 switch (naming) { 559 return output;
560 case NamingType.OVERWRITE: 560 }
561 path = new FileInfo (baseDir + Path.DirectorySeparatorChar + regionName); 561
562 return path.FullName; 562 ///
563 case NamingType.TIME: 563 /// Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error.
564 path = new FileInfo (baseDir + Path.DirectorySeparatorChar + regionName + GetTimeString () + ".oar"); 564 ///
565 return path.FullName; 565 private bool RunHeuristics(IScene region)
566 case NamingType.SEQUENTIAL: 566 {
567 path = new FileInfo (GetNextFile (baseDir, regionName)); 567 try
568 return path.FullName; 568 {
569 default: 569 return this.RunTimeDilationHeuristic(region) && this.RunAgentLimitHeuristic(region);
570 m_log.Warn ("VERY BAD: Unhandled case element " + naming.ToString ()); 570 }
571 break; 571 catch (Exception e)
572 } 572 {
573 573 m_log.Warn("[AUTO BACKUP]: Exception in RunHeuristics", e);
574 return path.FullName; 574 return false;
575 } 575 }
576 576 }
577 //Welcome to the TIME STRING. 4 CORNER INTEGERS, CUBES 4 QUAD MEMORY -- No 1 Integer God. 577
578 //(Terrible reference to <timecube.com>) 578 ///
579 //This format may turn out to be too unwieldy to keep... 579 /// If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5),
580 //Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? 580 /// then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR).
581 //Sequential numbers, right? Ugh. Almost makes TOO much sense. 581 /// AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy".
582 private string GetTimeString () 582 /// Return value of "true" ==> not too busy. Return value of "false" ==> too busy!
583 { 583 ///
584 StringWriter sw = new StringWriter (); 584 private bool RunTimeDilationHeuristic(IScene region)
585 sw.Write ("_"); 585 {
586 DateTime now = DateTime.Now; 586 string regionName = region.RegionInfo.RegionName;
587 sw.Write (now.Year); 587 return region.TimeDilation >=
588 sw.Write ("y_"); 588 this.m_configSource.Configs["AutoBackupModule"].GetFloat(
589 sw.Write (now.Month); 589 regionName + ".AutoBackupDilationThreshold", 0.5f);
590 sw.Write ("M_"); 590 }
591 sw.Write (now.Day); 591
592 sw.Write ("d_"); 592 ///
593 sw.Write (now.Hour); 593 /// If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10),
594 sw.Write ("h_"); 594 /// then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR).
595 sw.Write (now.Minute); 595 /// AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy".
596 sw.Write ("m_"); 596 /// Return value of "true" ==> not too busy. Return value of "false" ==> too busy!
597 sw.Write (now.Second); 597 ///
598 sw.Write ("s"); 598 private bool RunAgentLimitHeuristic(IScene region)
599 sw.Flush (); 599 {
600 string output = sw.ToString (); 600 string regionName = region.RegionInfo.RegionName;
601 sw.Close (); 601 try
602 return output; 602 {
603 } 603 Scene scene = (Scene) region;
604 604 /// TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful...
605 //Get the next logical file name 605 return scene.GetRootAgentCount() <=
606 //I really shouldn't put fields here, but for now.... ;) 606 this.m_configSource.Configs["AutoBackupModule"].GetInt(
607 private string m_dirName = null; 607 regionName + ".AutoBackupAgentThreshold", 10);
608 private string m_regionName = null; 608 }
609 private string GetNextFile (string dirName, string regionName) 609 catch (InvalidCastException ice)
610 { 610 {
611 FileInfo uniqueFile = null; 611 m_log.Debug(
612 m_dirName = dirName; 612 "[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!",
613 m_regionName = regionName; 613 ice);
614 long biggestExistingFile = HalfIntervalMaximize (1, FileExistsTest); 614 return true;
615 biggestExistingFile++; 615 /// Non-obstructionist safest answer...
616 //We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest. 616 }
617 uniqueFile = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + biggestExistingFile + ".oar"); 617 }
618 if (uniqueFile.Exists) { 618
619 //Congratulations, your strange deletion patterns fooled my half-interval search into picking an existing file! 619 private static void ExecuteScript(string scriptName, string savePath)
620 //Now you get to pay the performance cost :) 620 {
621 uniqueFile = UniqueFileSearchLinear (biggestExistingFile); 621 //Fast path out
622 } 622 if (scriptName == null || scriptName.Length <= 0)
623 623 {
624 return uniqueFile.FullName; 624 return;
625 } 625 }
626 626
627 /* 627 try
628 * Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error. 628 {
629 * */ 629 FileInfo fi = new FileInfo(scriptName);
630 private bool RunHeuristics (IScene region) 630 if (fi.Exists)
631 { 631 {
632 try { 632 ProcessStartInfo psi = new ProcessStartInfo(scriptName);
633 return RunTimeDilationHeuristic (region) && RunAgentLimitHeuristic (region); 633 psi.Arguments = savePath;
634 } catch (Exception e) { 634 psi.CreateNoWindow = true;
635 m_log.Warn ("[AUTO BACKUP]: Exception in RunHeuristics", e); 635 Process proc = Process.Start(psi);
636 return false; 636 proc.ErrorDataReceived += HandleProcErrorDataReceived;
637 } 637 }
638 } 638 }
639 639 catch (Exception e)
640 /* 640 {
641 * If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5), 641 m_log.Warn(
642 * then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR). 642 "Exception encountered when trying to run script for oar backup " + savePath, e);
643 * AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy". 643 }
644 * Return value of "true" ==> not too busy. Return value of "false" ==> too busy! 644 }
645 * */ 645
646 private bool RunTimeDilationHeuristic (IScene region) 646 private static void HandleProcErrorDataReceived(object sender, DataReceivedEventArgs e)
647 { 647 {
648 string regionName = region.RegionInfo.RegionName; 648 m_log.Warn("ExecuteScript hook " + ((Process) sender).ProcessName +
649 return region.TimeDilation >= m_configSource.Configs["AutoBackupModule"].GetFloat (regionName + ".AutoBackupDilationThreshold", 0.5f); 649 " is yacking on stderr: " + e.Data);
650 } 650 }
651 651
652 /* 652 private void StopAllTimers()
653 * If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10), 653 {
654 * then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR). 654 foreach (Timer t in this.m_timerMap.Keys)
655 * AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy". 655 {
656 * Return value of "true" ==> not too busy. Return value of "false" ==> too busy! 656 t.Close();
657 * */ 657 }
658 private bool RunAgentLimitHeuristic (IScene region) 658 this.m_closed = true;
659 { 659 }
660 string regionName = region.RegionInfo.RegionName; 660
661 try { 661 private static string GetNextFile(string dirName, string regionName)
662 Scene scene = (Scene)region; 662 {
663 //TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful... 663 FileInfo uniqueFile = null;
664 return scene.GetRootAgentCount () <= m_configSource.Configs["AutoBackupModule"].GetInt (regionName + ".AutoBackupAgentThreshold", 10); 664 long biggestExistingFile = GetNextOarFileNumber(dirName, regionName);
665 } catch (InvalidCastException ice) { 665 biggestExistingFile++;
666 m_log.Debug ("[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!"); 666 //We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest.
667 return true; 667 uniqueFile =
668 //Non-obstructionist safest answer... 668 new FileInfo(dirName + Path.DirectorySeparatorChar + regionName + "_" +
669 } 669 biggestExistingFile + ".oar");
670 } 670 return uniqueFile.FullName;
671 671 }
672 private void ExecuteScript (string scriptName, string savePath) 672
673 { 673 private static string BuildOarPath(string regionName, string baseDir, NamingType naming)
674 //Fast path out 674 {
675 if (scriptName == null || scriptName.Length <= 0) 675 FileInfo path = null;
676 return; 676 switch (naming)
677 677 {
678 try { 678 case NamingType.Overwrite:
679 FileInfo fi = new FileInfo (scriptName); 679 path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + ".oar");
680 if (fi.Exists) { 680 return path.FullName;
681 ProcessStartInfo psi = new ProcessStartInfo (scriptName); 681 case NamingType.Time:
682 psi.Arguments = savePath; 682 path =
683 psi.CreateNoWindow = true; 683 new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName +
684 Process proc = Process.Start (psi); 684 GetTimeString() + ".oar");
685 proc.ErrorDataReceived += HandleProcErrorDataReceived; 685 return path.FullName;
686 } 686 case NamingType.Sequential:
687 } catch (Exception e) { 687 /// All codepaths in GetNextFile should return a file name ending in .oar
688 m_log.Warn ("Exception encountered when trying to run script for oar backup " + savePath, e); 688 path = new FileInfo(GetNextFile(baseDir, regionName));
689 } 689 return path.FullName;
690 } 690 default:
691 691 m_log.Warn("VERY BAD: Unhandled case element " + naming);
692 void HandleProcErrorDataReceived (object sender, DataReceivedEventArgs e) 692 break;
693 { 693 }
694 m_log.Warn ("ExecuteScript hook " + ((Process)sender).ProcessName + " is yacking on stderr: " + e.Data); 694
695 } 695 return null;
696 696 }
697 private void StopAllTimers () 697
698 { 698 private static long GetNextOarFileNumber(string dirName, string regionName)
699 foreach (Timer t in timerMap.Keys) { 699 {
700 t.Close (); 700 long retval = 1;
701 } 701
702 m_closed = true; 702 DirectoryInfo di = new DirectoryInfo(dirName);
703 } 703 FileInfo[] fi = di.GetFiles(regionName, SearchOption.TopDirectoryOnly);
704 704 Array.Sort(fi, (f1, f2) => StringComparer.CurrentCultureIgnoreCase.Compare(f1.Name, f2.Name));
705 /* Find the largest value for which the predicate returns true. 705
706 * We use a bisection algorithm (half interval) to make the algorithm scalable. 706 if (fi.LongLength > 0)
707 * The worst-case complexity is about O(log(n)^2) in practice. 707 {
708 * Only for extremely small values (under 10) do you notice it taking more iterations than a linear search. 708 long subtract = 1L;
709 * The number of predicate invocations only hits a few hundred when the maximized value 709 bool worked = false;
710 * is in the tens of millions, so prepare for the predicate to be invoked between 10 and 100 times. 710 Regex reg = new Regex(regionName + "_([0-9])+" + ".oar");
711 * And of course it is fantastic with powers of 2, which are densely packed in values under 100 anyway. 711
712 * The Predicate<long> parameter must be a function that accepts a long and returns a bool. 712 while (!worked && subtract <= fi.LongLength)
713 * */ 713 {
714 public long HalfIntervalMaximize (long start, Predicate<long> pred) 714 /// Pick the file with the last natural ordering
715 { 715 string biggestFileName = fi[fi.LongLength - subtract].Name;
716 long prev = start, curr = start, biggest = 0; 716 MatchCollection matches = reg.Matches(biggestFileName);
717 717 long l = 1;
718 if (start < 0) 718 if (matches.Count > 0 && matches[0].Groups.Count > 0)
719 throw new IndexOutOfRangeException ("Start value for HalfIntervalMaximize must be non-negative"); 719 {
720 720 try
721 do { 721 {
722 if (pred (curr)) { 722 long.TryParse(matches[0].Groups[1].Value, out l);
723 if (curr > biggest) { 723 retval = l;
724 biggest = curr; 724 worked = true;
725 } 725 }
726 prev = curr; 726 catch (FormatException fe)
727 if (curr == 0) { 727 {
728 //Special case because 0 * 2 = 0 :) 728 m_log.Warn(
729 curr = 1; 729 "[AUTO BACKUP]: Error: Can't parse long value from file name to determine next OAR backup file number!",
730 } else { 730 fe);
731 //Look deeper 731 subtract++;
732 curr *= 2; 732 }
733 } 733 }
734 } else { 734 else
735 // We went too far, back off halfway 735 {
736 curr = (curr + prev) / 2; 736 subtract++;
737 } 737 }
738 } while (curr - prev > 0); 738 }
739 739 }
740 return biggest; 740 return retval;
741 } 741 }
742 742 }
743 public bool FileExistsTest (long num) 743}
744 { 744
745 FileInfo test = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + num + ".oar"); 745
746 return test.Exists;
747 }
748
749
750 //Very slow, hence why we try the HalfIntervalMaximize first!
751 public FileInfo UniqueFileSearchLinear (long start)
752 {
753 long l = start;
754 FileInfo retval = null;
755 do {
756 retval = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + (l++) + ".oar");
757 } while (retval.Exists);
758
759 return retval;
760 }
761 }
762
763}
764