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