1
0

AutoBackupModule.cs 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926
  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. using System;
  28. using System.Collections.Generic;
  29. using System.Diagnostics;
  30. using System.IO;
  31. using System.Reflection;
  32. using System.Timers;
  33. using System.Text.RegularExpressions;
  34. using log4net;
  35. using Nini.Config;
  36. using OpenSim.Framework;
  37. using OpenSim.Region.Framework.Interfaces;
  38. using OpenSim.Region.Framework.Scenes;
  39. namespace OpenSim.Region.OptionalModules.World.AutoBackup
  40. {
  41. /// <summary>
  42. /// Choose between ways of naming the backup files that are generated.
  43. /// </summary>
  44. /// <remarks>Time: OARs are named by a timestamp.
  45. /// Sequential: OARs are named by counting (Region_1.oar, Region_2.oar, etc.)
  46. /// Overwrite: Only one file per region is created; it's overwritten each time a backup is made.</remarks>
  47. public enum NamingType
  48. {
  49. Time,
  50. Sequential,
  51. Overwrite
  52. }
  53. ///<summary>
  54. /// AutoBackupModule: save OAR region backups to disk periodically
  55. /// </summary>
  56. /// <remarks>
  57. /// Config Settings Documentation.
  58. /// Each configuration setting can be specified in two places: OpenSim.ini or Regions.ini.
  59. /// If specified in Regions.ini, the settings should be within the region's section name.
  60. /// If specified in OpenSim.ini, the settings should be within the [AutoBackupModule] section.
  61. /// Region-specific settings take precedence.
  62. ///
  63. /// AutoBackupModuleEnabled: True/False. Default: False. If True, use the auto backup module. This setting does not support per-region basis.
  64. /// All other settings under [AutoBackupModule] are ignored if AutoBackupModuleEnabled is false, even per-region settings!
  65. /// AutoBackup: True/False. Default: False. If True, activate auto backup functionality.
  66. /// This is the only required option for enabling auto-backup; the other options have sane defaults.
  67. /// If False for a particular region, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored.
  68. /// If False globally (the default), only regions that specifically override it in Regions.ini will get AutoBackup functionality.
  69. /// AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours).
  70. /// The number of minutes between each backup attempt.
  71. /// If a negative or zero value is given, it is equivalent to setting AutoBackup = False.
  72. /// AutoBackupBusyCheck: True/False. Default: True.
  73. /// If True, we will only take an auto-backup if a set of conditions are met.
  74. /// These conditions are heuristics to try and avoid taking a backup when the sim is busy.
  75. /// AutoBackupScript: String. Default: not specified (disabled).
  76. /// File path to an executable script or binary to run when an automatic backup is taken.
  77. /// The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary.
  78. /// Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results!
  79. /// argv[1] of the executed file/script will be the file name of the generated OAR.
  80. /// If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console.
  81. /// AutoBackupNaming: string. Default: Time.
  82. /// One of three strings (case insensitive):
  83. /// "Time": Current timestamp is appended to file name. An existing file will never be overwritten.
  84. /// "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.
  85. /// "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file.
  86. /// AutoBackupDir: String. Default: "." (the current directory).
  87. /// A directory (absolute or relative) where backups should be saved.
  88. /// AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass.
  89. /// If the time dilation is below this value, don't take a backup right now.
  90. /// AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass.
  91. /// If the number of agents is greater than this value, don't take a backup right now
  92. /// Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions.
  93. /// Also helps if you don't want AutoBackup at all.
  94. /// </remarks>
  95. public class AutoBackupModule : ISharedRegionModule
  96. {
  97. private static readonly ILog m_log =
  98. LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  99. private readonly Dictionary<Guid, IScene> m_pendingSaves = new Dictionary<Guid, IScene>(1);
  100. private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState();
  101. private readonly Dictionary<IScene, AutoBackupModuleState> m_states =
  102. new Dictionary<IScene, AutoBackupModuleState>(1);
  103. private readonly Dictionary<Timer, List<IScene>> m_timerMap =
  104. new Dictionary<Timer, List<IScene>>(1);
  105. private readonly Dictionary<double, Timer> m_timers = new Dictionary<double, Timer>(1);
  106. private delegate T DefaultGetter<T>(string settingName, T defaultValue);
  107. private bool m_enabled;
  108. /// <summary>
  109. /// Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState!
  110. /// </summary>
  111. private bool m_closed;
  112. private IConfigSource m_configSource;
  113. /// <summary>
  114. /// Required by framework.
  115. /// </summary>
  116. public bool IsSharedModule
  117. {
  118. get { return true; }
  119. }
  120. #region ISharedRegionModule Members
  121. /// <summary>
  122. /// Identifies the module to the system.
  123. /// </summary>
  124. string IRegionModuleBase.Name
  125. {
  126. get { return "AutoBackupModule"; }
  127. }
  128. /// <summary>
  129. /// We don't implement an interface, this is a single-use module.
  130. /// </summary>
  131. Type IRegionModuleBase.ReplaceableInterface
  132. {
  133. get { return null; }
  134. }
  135. /// <summary>
  136. /// Called once in the lifetime of the module at startup.
  137. /// </summary>
  138. /// <param name="source">The input config source for OpenSim.ini.</param>
  139. void IRegionModuleBase.Initialise(IConfigSource source)
  140. {
  141. // Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module
  142. this.m_configSource = source;
  143. IConfig moduleConfig = source.Configs["AutoBackupModule"];
  144. if (moduleConfig == null)
  145. {
  146. this.m_enabled = false;
  147. return;
  148. }
  149. else
  150. {
  151. this.m_enabled = moduleConfig.GetBoolean("AutoBackupModuleEnabled", false);
  152. if (this.m_enabled)
  153. {
  154. m_log.Info("[AUTO BACKUP]: AutoBackupModule enabled");
  155. }
  156. else
  157. {
  158. return;
  159. }
  160. }
  161. Timer defTimer = new Timer(43200000);
  162. this.m_defaultState.Timer = defTimer;
  163. this.m_timers.Add(43200000, defTimer);
  164. defTimer.Elapsed += this.HandleElapsed;
  165. defTimer.AutoReset = true;
  166. defTimer.Start();
  167. AutoBackupModuleState abms = this.ParseConfig(null, true);
  168. m_log.Debug("[AUTO BACKUP]: Here is the default config:");
  169. m_log.Debug(abms.ToString());
  170. }
  171. /// <summary>
  172. /// Called once at de-init (sim shutting down).
  173. /// </summary>
  174. void IRegionModuleBase.Close()
  175. {
  176. if (!this.m_enabled)
  177. {
  178. return;
  179. }
  180. // We don't want any timers firing while the sim's coming down; strange things may happen.
  181. this.StopAllTimers();
  182. }
  183. /// <summary>
  184. /// Currently a no-op for AutoBackup because we have to wait for region to be fully loaded.
  185. /// </summary>
  186. /// <param name="scene"></param>
  187. void IRegionModuleBase.AddRegion(Scene scene)
  188. {
  189. }
  190. /// <summary>
  191. /// Here we just clean up some resources and stop the OAR backup (if any) for the given scene.
  192. /// </summary>
  193. /// <param name="scene">The scene (region) to stop performing AutoBackup on.</param>
  194. void IRegionModuleBase.RemoveRegion(Scene scene)
  195. {
  196. if (!this.m_enabled)
  197. {
  198. return;
  199. }
  200. if (this.m_states.ContainsKey(scene))
  201. {
  202. AutoBackupModuleState abms = this.m_states[scene];
  203. // Remove this scene out of the timer map list
  204. Timer timer = abms.Timer;
  205. List<IScene> list = this.m_timerMap[timer];
  206. list.Remove(scene);
  207. // Shut down the timer if this was the last scene for the timer
  208. if (list.Count == 0)
  209. {
  210. this.m_timerMap.Remove(timer);
  211. this.m_timers.Remove(timer.Interval);
  212. timer.Close();
  213. }
  214. this.m_states.Remove(scene);
  215. }
  216. }
  217. /// <summary>
  218. /// Most interesting/complex code paths in AutoBackup begin here.
  219. /// We read lots of Nini config, maybe set a timer, add members to state tracking Dictionaries, etc.
  220. /// </summary>
  221. /// <param name="scene">The scene to (possibly) perform AutoBackup on.</param>
  222. void IRegionModuleBase.RegionLoaded(Scene scene)
  223. {
  224. if (!this.m_enabled)
  225. {
  226. return;
  227. }
  228. // This really ought not to happen, but just in case, let's pretend it didn't...
  229. if (scene == null)
  230. {
  231. return;
  232. }
  233. AutoBackupModuleState abms = this.ParseConfig(scene, false);
  234. m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName);
  235. m_log.Debug((abms == null ? "DEFAULT" : abms.ToString()));
  236. }
  237. /// <summary>
  238. /// Currently a no-op.
  239. /// </summary>
  240. void ISharedRegionModule.PostInitialise()
  241. {
  242. }
  243. #endregion
  244. /// <summary>
  245. /// Set up internal state for a given scene. Fairly complex code.
  246. /// When this method returns, we've started auto-backup timers, put members in Dictionaries, and created a State object for this scene.
  247. /// </summary>
  248. /// <param name="scene">The scene to look at.</param>
  249. /// <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>
  250. /// <returns>An AutoBackupModuleState contains most information you should need to know relevant to auto-backup, as applicable to a single region.</returns>
  251. private AutoBackupModuleState ParseConfig(IScene scene, bool parseDefault)
  252. {
  253. string sRegionName;
  254. string sRegionLabel;
  255. // string prepend;
  256. AutoBackupModuleState state;
  257. if (parseDefault)
  258. {
  259. sRegionName = null;
  260. sRegionLabel = "DEFAULT";
  261. // prepend = "";
  262. state = this.m_defaultState;
  263. }
  264. else
  265. {
  266. sRegionName = scene.RegionInfo.RegionName;
  267. sRegionLabel = sRegionName;
  268. // prepend = sRegionName + ".";
  269. state = null;
  270. }
  271. // Read the config settings and set variables.
  272. IConfig regionConfig = (scene != null ? scene.Config.Configs[sRegionName] : null);
  273. IConfig config = this.m_configSource.Configs["AutoBackupModule"];
  274. if (config == null)
  275. {
  276. // defaultState would be disabled too if the section doesn't exist.
  277. state = this.m_defaultState;
  278. return state;
  279. }
  280. bool tmpEnabled = ResolveBoolean("AutoBackup", this.m_defaultState.Enabled, config, regionConfig);
  281. if (state == null && tmpEnabled != this.m_defaultState.Enabled)
  282. //Varies from default state
  283. {
  284. state = new AutoBackupModuleState();
  285. }
  286. if (state != null)
  287. {
  288. state.Enabled = tmpEnabled;
  289. }
  290. // If you don't want AutoBackup, we stop.
  291. if ((state == null && !this.m_defaultState.Enabled) || (state != null && !state.Enabled))
  292. {
  293. return state;
  294. }
  295. else
  296. {
  297. m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED.");
  298. }
  299. // Borrow an existing timer if one exists for the same interval; otherwise, make a new one.
  300. double interval =
  301. this.ResolveDouble("AutoBackupInterval", this.m_defaultState.IntervalMinutes,
  302. config, regionConfig) * 60000.0;
  303. if (state == null && interval != this.m_defaultState.IntervalMinutes*60000.0)
  304. {
  305. state = new AutoBackupModuleState();
  306. }
  307. if (this.m_timers.ContainsKey(interval))
  308. {
  309. if (state != null)
  310. {
  311. state.Timer = this.m_timers[interval];
  312. }
  313. m_log.Debug("[AUTO BACKUP]: Reusing timer for " + interval + " msec for region " +
  314. sRegionLabel);
  315. }
  316. else
  317. {
  318. // 0 or negative interval == do nothing.
  319. if (interval <= 0.0 && state != null)
  320. {
  321. state.Enabled = false;
  322. return state;
  323. }
  324. if (state == null)
  325. {
  326. state = new AutoBackupModuleState();
  327. }
  328. Timer tim = new Timer(interval);
  329. state.Timer = tim;
  330. //Milliseconds -> minutes
  331. this.m_timers.Add(interval, tim);
  332. tim.Elapsed += this.HandleElapsed;
  333. tim.AutoReset = true;
  334. tim.Start();
  335. }
  336. // Add the current region to the list of regions tied to this timer.
  337. if (scene != null)
  338. {
  339. if (state != null)
  340. {
  341. if (this.m_timerMap.ContainsKey(state.Timer))
  342. {
  343. this.m_timerMap[state.Timer].Add(scene);
  344. }
  345. else
  346. {
  347. List<IScene> scns = new List<IScene>(1);
  348. scns.Add(scene);
  349. this.m_timerMap.Add(state.Timer, scns);
  350. }
  351. }
  352. else
  353. {
  354. if (this.m_timerMap.ContainsKey(this.m_defaultState.Timer))
  355. {
  356. this.m_timerMap[this.m_defaultState.Timer].Add(scene);
  357. }
  358. else
  359. {
  360. List<IScene> scns = new List<IScene>(1);
  361. scns.Add(scene);
  362. this.m_timerMap.Add(this.m_defaultState.Timer, scns);
  363. }
  364. }
  365. }
  366. bool tmpBusyCheck = ResolveBoolean("AutoBackupBusyCheck",
  367. this.m_defaultState.BusyCheck, config, regionConfig);
  368. if (state == null && tmpBusyCheck != this.m_defaultState.BusyCheck)
  369. {
  370. state = new AutoBackupModuleState();
  371. }
  372. if (state != null)
  373. {
  374. state.BusyCheck = tmpBusyCheck;
  375. }
  376. // Set file naming algorithm
  377. string stmpNamingType = ResolveString("AutoBackupNaming",
  378. this.m_defaultState.NamingType.ToString(), config, regionConfig);
  379. NamingType tmpNamingType;
  380. if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase))
  381. {
  382. tmpNamingType = NamingType.Time;
  383. }
  384. else if (stmpNamingType.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase))
  385. {
  386. tmpNamingType = NamingType.Sequential;
  387. }
  388. else if (stmpNamingType.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase))
  389. {
  390. tmpNamingType = NamingType.Overwrite;
  391. }
  392. else
  393. {
  394. m_log.Warn("Unknown naming type specified for region " + sRegionLabel + ": " +
  395. stmpNamingType);
  396. tmpNamingType = NamingType.Time;
  397. }
  398. if (state == null && tmpNamingType != this.m_defaultState.NamingType)
  399. {
  400. state = new AutoBackupModuleState();
  401. }
  402. if (state != null)
  403. {
  404. state.NamingType = tmpNamingType;
  405. }
  406. string tmpScript = ResolveString("AutoBackupScript",
  407. this.m_defaultState.Script, config, regionConfig);
  408. if (state == null && tmpScript != this.m_defaultState.Script)
  409. {
  410. state = new AutoBackupModuleState();
  411. }
  412. if (state != null)
  413. {
  414. state.Script = tmpScript;
  415. }
  416. string tmpBackupDir = ResolveString("AutoBackupDir", ".", config, regionConfig);
  417. if (state == null && tmpBackupDir != this.m_defaultState.BackupDir)
  418. {
  419. state = new AutoBackupModuleState();
  420. }
  421. if (state != null)
  422. {
  423. state.BackupDir = tmpBackupDir;
  424. // Let's give the user some convenience and auto-mkdir
  425. if (state.BackupDir != ".")
  426. {
  427. try
  428. {
  429. DirectoryInfo dirinfo = new DirectoryInfo(state.BackupDir);
  430. if (!dirinfo.Exists)
  431. {
  432. dirinfo.Create();
  433. }
  434. }
  435. catch (Exception e)
  436. {
  437. m_log.Warn(
  438. "BAD NEWS. You won't be able to save backups to directory " +
  439. state.BackupDir +
  440. " because it doesn't exist or there's a permissions issue with it. Here's the exception.",
  441. e);
  442. }
  443. }
  444. }
  445. return state;
  446. }
  447. /// <summary>
  448. /// Helper function for ParseConfig.
  449. /// </summary>
  450. /// <param name="settingName"></param>
  451. /// <param name="defaultValue"></param>
  452. /// <param name="global"></param>
  453. /// <param name="local"></param>
  454. /// <returns></returns>
  455. private bool ResolveBoolean(string settingName, bool defaultValue, IConfig global, IConfig local)
  456. {
  457. if(local != null)
  458. {
  459. return local.GetBoolean(settingName, global.GetBoolean(settingName, defaultValue));
  460. }
  461. else
  462. {
  463. return global.GetBoolean(settingName, defaultValue);
  464. }
  465. }
  466. /// <summary>
  467. /// Helper function for ParseConfig.
  468. /// </summary>
  469. /// <param name="settingName"></param>
  470. /// <param name="defaultValue"></param>
  471. /// <param name="global"></param>
  472. /// <param name="local"></param>
  473. /// <returns></returns>
  474. private double ResolveDouble(string settingName, double defaultValue, IConfig global, IConfig local)
  475. {
  476. if (local != null)
  477. {
  478. return local.GetDouble(settingName, global.GetDouble(settingName, defaultValue));
  479. }
  480. else
  481. {
  482. return global.GetDouble(settingName, defaultValue);
  483. }
  484. }
  485. /// <summary>
  486. /// Helper function for ParseConfig.
  487. /// </summary>
  488. /// <param name="settingName"></param>
  489. /// <param name="defaultValue"></param>
  490. /// <param name="global"></param>
  491. /// <param name="local"></param>
  492. /// <returns></returns>
  493. private int ResolveInt(string settingName, int defaultValue, IConfig global, IConfig local)
  494. {
  495. if (local != null)
  496. {
  497. return local.GetInt(settingName, global.GetInt(settingName, defaultValue));
  498. }
  499. else
  500. {
  501. return global.GetInt(settingName, defaultValue);
  502. }
  503. }
  504. /// <summary>
  505. /// Helper function for ParseConfig.
  506. /// </summary>
  507. /// <param name="settingName"></param>
  508. /// <param name="defaultValue"></param>
  509. /// <param name="global"></param>
  510. /// <param name="local"></param>
  511. /// <returns></returns>
  512. private string ResolveString(string settingName, string defaultValue, IConfig global, IConfig local)
  513. {
  514. if (local != null)
  515. {
  516. return local.GetString(settingName, global.GetString(settingName, defaultValue));
  517. }
  518. else
  519. {
  520. return global.GetString(settingName, defaultValue);
  521. }
  522. }
  523. /// <summary>
  524. /// Called when any auto-backup timer expires. This starts the code path for actually performing a backup.
  525. /// </summary>
  526. /// <param name="sender"></param>
  527. /// <param name="e"></param>
  528. private void HandleElapsed(object sender, ElapsedEventArgs e)
  529. {
  530. // TODO: heuristic thresholds are per-region, so we should probably run heuristics once per region
  531. // XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to
  532. // check whether the region is too busy! Especially on sims with LOTS of regions.
  533. // Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible,
  534. // but would allow us to be semantically correct while being easier on perf.
  535. // Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi...
  536. // Alternative 3: Don't support per-region heuristics at all; just accept them as a global only parameter.
  537. // Since this is pretty experimental, I haven't decided which alternative makes the most sense.
  538. if (this.m_closed)
  539. {
  540. return;
  541. }
  542. bool heuristicsRun = false;
  543. bool heuristicsPassed = false;
  544. if (!this.m_timerMap.ContainsKey((Timer) sender))
  545. {
  546. m_log.Debug("Code-up error: timerMap doesn't contain timer " + sender);
  547. }
  548. List<IScene> tmap = this.m_timerMap[(Timer) sender];
  549. if (tmap != null && tmap.Count > 0)
  550. {
  551. foreach (IScene scene in tmap)
  552. {
  553. AutoBackupModuleState state = this.m_states[scene];
  554. bool heuristics = state.BusyCheck;
  555. // Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region.
  556. if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics)
  557. {
  558. this.DoRegionBackup(scene);
  559. // Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off!
  560. }
  561. else if (heuristicsRun)
  562. {
  563. m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " +
  564. scene.RegionInfo.RegionName + " right now.");
  565. continue;
  566. // Logical Deduction: heuristics are on but haven't been run
  567. }
  568. else
  569. {
  570. heuristicsPassed = this.RunHeuristics(scene);
  571. heuristicsRun = true;
  572. if (!heuristicsPassed)
  573. {
  574. m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " +
  575. scene.RegionInfo.RegionName + " right now.");
  576. continue;
  577. }
  578. this.DoRegionBackup(scene);
  579. }
  580. }
  581. }
  582. }
  583. /// <summary>
  584. /// Save an OAR, register for the callback for when it's done, then call the AutoBackupScript (if applicable).
  585. /// </summary>
  586. /// <param name="scene"></param>
  587. private void DoRegionBackup(IScene scene)
  588. {
  589. if (scene.RegionStatus != RegionStatus.Up)
  590. {
  591. // We won't backup a region that isn't operating normally.
  592. m_log.Warn("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName +
  593. " because its status is " + scene.RegionStatus);
  594. return;
  595. }
  596. AutoBackupModuleState state = this.m_states[scene];
  597. IRegionArchiverModule iram = scene.RequestModuleInterface<IRegionArchiverModule>();
  598. string savePath = BuildOarPath(scene.RegionInfo.RegionName,
  599. state.BackupDir,
  600. state.NamingType);
  601. if (savePath == null)
  602. {
  603. m_log.Warn("[AUTO BACKUP]: savePath is null in HandleElapsed");
  604. return;
  605. }
  606. Guid guid = Guid.NewGuid();
  607. m_pendingSaves.Add(guid, scene);
  608. state.LiveRequests.Add(guid, savePath);
  609. ((Scene) scene).EventManager.OnOarFileSaved += new EventManager.OarFileSaved(EventManager_OnOarFileSaved);
  610. iram.ArchiveRegion(savePath, guid, null);
  611. }
  612. /// <summary>
  613. /// Called by the Event Manager when the OnOarFileSaved event is fired.
  614. /// </summary>
  615. /// <param name="guid"></param>
  616. /// <param name="message"></param>
  617. void EventManager_OnOarFileSaved(Guid guid, string message)
  618. {
  619. // Ignore if the OAR save is being done by some other part of the system
  620. if (m_pendingSaves.ContainsKey(guid))
  621. {
  622. AutoBackupModuleState abms = m_states[(m_pendingSaves[guid])];
  623. ExecuteScript(abms.Script, abms.LiveRequests[guid]);
  624. m_pendingSaves.Remove(guid);
  625. abms.LiveRequests.Remove(guid);
  626. }
  627. }
  628. /// <summary>This format may turn out to be too unwieldy to keep...
  629. /// Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID?
  630. /// Sequential numbers, right? We support those, too!</summary>
  631. private static string GetTimeString()
  632. {
  633. StringWriter sw = new StringWriter();
  634. sw.Write("_");
  635. DateTime now = DateTime.Now;
  636. sw.Write(now.Year);
  637. sw.Write("y_");
  638. sw.Write(now.Month);
  639. sw.Write("M_");
  640. sw.Write(now.Day);
  641. sw.Write("d_");
  642. sw.Write(now.Hour);
  643. sw.Write("h_");
  644. sw.Write(now.Minute);
  645. sw.Write("m_");
  646. sw.Write(now.Second);
  647. sw.Write("s");
  648. sw.Flush();
  649. string output = sw.ToString();
  650. sw.Close();
  651. return output;
  652. }
  653. /// <summary>Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error.</summary>
  654. private bool RunHeuristics(IScene region)
  655. {
  656. try
  657. {
  658. return this.RunTimeDilationHeuristic(region) && this.RunAgentLimitHeuristic(region);
  659. }
  660. catch (Exception e)
  661. {
  662. m_log.Warn("[AUTO BACKUP]: Exception in RunHeuristics", e);
  663. return false;
  664. }
  665. }
  666. /// <summary>
  667. /// If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5),
  668. /// then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR).
  669. /// AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy".
  670. /// </summary>
  671. /// <param name="region"></param>
  672. /// <returns>Returns true if we're not too busy; false means we've got worse time dilation than the threshold.</returns>
  673. private bool RunTimeDilationHeuristic(IScene region)
  674. {
  675. string regionName = region.RegionInfo.RegionName;
  676. return region.TimeDilation >=
  677. this.m_configSource.Configs["AutoBackupModule"].GetFloat(
  678. regionName + ".AutoBackupDilationThreshold", 0.5f);
  679. }
  680. /// <summary>
  681. /// If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10),
  682. /// then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR).
  683. /// AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy".
  684. /// </summary>
  685. /// <param name="region"></param>
  686. /// <returns>Returns true if we're not too busy; false means we've got more agents on the sim than the threshold.</returns>
  687. private bool RunAgentLimitHeuristic(IScene region)
  688. {
  689. string regionName = region.RegionInfo.RegionName;
  690. try
  691. {
  692. Scene scene = (Scene) region;
  693. // TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful...
  694. return scene.GetRootAgentCount() <=
  695. this.m_configSource.Configs["AutoBackupModule"].GetInt(
  696. regionName + ".AutoBackupAgentThreshold", 10);
  697. }
  698. catch (InvalidCastException ice)
  699. {
  700. m_log.Debug(
  701. "[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!",
  702. ice);
  703. return true;
  704. // Non-obstructionist safest answer...
  705. }
  706. }
  707. /// <summary>
  708. /// Run the script or executable specified by the "AutoBackupScript" config setting.
  709. /// Of course this is a security risk if you let anyone modify OpenSim.ini and they want to run some nasty bash script.
  710. /// But there are plenty of other nasty things that can be done with an untrusted OpenSim.ini, such as running high threat level scripting functions.
  711. /// </summary>
  712. /// <param name="scriptName"></param>
  713. /// <param name="savePath"></param>
  714. private static void ExecuteScript(string scriptName, string savePath)
  715. {
  716. // Do nothing if there's no script.
  717. if (scriptName == null || scriptName.Length <= 0)
  718. {
  719. return;
  720. }
  721. try
  722. {
  723. FileInfo fi = new FileInfo(scriptName);
  724. if (fi.Exists)
  725. {
  726. ProcessStartInfo psi = new ProcessStartInfo(scriptName);
  727. psi.Arguments = savePath;
  728. psi.CreateNoWindow = true;
  729. Process proc = Process.Start(psi);
  730. proc.ErrorDataReceived += HandleProcErrorDataReceived;
  731. }
  732. }
  733. catch (Exception e)
  734. {
  735. m_log.Warn(
  736. "Exception encountered when trying to run script for oar backup " + savePath, e);
  737. }
  738. }
  739. /// <summary>
  740. /// Called if a running script process writes to stderr.
  741. /// </summary>
  742. /// <param name="sender"></param>
  743. /// <param name="e"></param>
  744. private static void HandleProcErrorDataReceived(object sender, DataReceivedEventArgs e)
  745. {
  746. m_log.Warn("ExecuteScript hook " + ((Process) sender).ProcessName +
  747. " is yacking on stderr: " + e.Data);
  748. }
  749. /// <summary>
  750. /// Quickly stop all timers from firing.
  751. /// </summary>
  752. private void StopAllTimers()
  753. {
  754. foreach (Timer t in this.m_timerMap.Keys)
  755. {
  756. t.Close();
  757. }
  758. this.m_closed = true;
  759. }
  760. /// <summary>
  761. /// Determine the next unique filename by number, for "Sequential" AutoBackupNamingType.
  762. /// </summary>
  763. /// <param name="dirName"></param>
  764. /// <param name="regionName"></param>
  765. /// <returns></returns>
  766. private static string GetNextFile(string dirName, string regionName)
  767. {
  768. FileInfo uniqueFile = null;
  769. long biggestExistingFile = GetNextOarFileNumber(dirName, regionName);
  770. biggestExistingFile++;
  771. // We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest.
  772. uniqueFile =
  773. new FileInfo(dirName + Path.DirectorySeparatorChar + regionName + "_" +
  774. biggestExistingFile + ".oar");
  775. return uniqueFile.FullName;
  776. }
  777. /// <summary>
  778. /// Top-level method for creating an absolute path to an OAR backup file based on what naming scheme the user wants.
  779. /// </summary>
  780. /// <param name="regionName">Name of the region to save.</param>
  781. /// <param name="baseDir">Absolute or relative path to the directory where the file should reside.</param>
  782. /// <param name="naming">The naming scheme for the file name.</param>
  783. /// <returns></returns>
  784. private static string BuildOarPath(string regionName, string baseDir, NamingType naming)
  785. {
  786. FileInfo path = null;
  787. switch (naming)
  788. {
  789. case NamingType.Overwrite:
  790. path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + ".oar");
  791. return path.FullName;
  792. case NamingType.Time:
  793. path =
  794. new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName +
  795. GetTimeString() + ".oar");
  796. return path.FullName;
  797. case NamingType.Sequential:
  798. // All codepaths in GetNextFile should return a file name ending in .oar
  799. path = new FileInfo(GetNextFile(baseDir, regionName));
  800. return path.FullName;
  801. default:
  802. m_log.Warn("VERY BAD: Unhandled case element " + naming);
  803. break;
  804. }
  805. return null;
  806. }
  807. /// <summary>
  808. /// Helper function for Sequential file naming type (see BuildOarPath and GetNextFile).
  809. /// </summary>
  810. /// <param name="dirName"></param>
  811. /// <param name="regionName"></param>
  812. /// <returns></returns>
  813. private static long GetNextOarFileNumber(string dirName, string regionName)
  814. {
  815. long retval = 1;
  816. DirectoryInfo di = new DirectoryInfo(dirName);
  817. FileInfo[] fi = di.GetFiles(regionName, SearchOption.TopDirectoryOnly);
  818. Array.Sort(fi, (f1, f2) => StringComparer.CurrentCultureIgnoreCase.Compare(f1.Name, f2.Name));
  819. if (fi.LongLength > 0)
  820. {
  821. long subtract = 1L;
  822. bool worked = false;
  823. Regex reg = new Regex(regionName + "_([0-9])+" + ".oar");
  824. while (!worked && subtract <= fi.LongLength)
  825. {
  826. // Pick the file with the last natural ordering
  827. string biggestFileName = fi[fi.LongLength - subtract].Name;
  828. MatchCollection matches = reg.Matches(biggestFileName);
  829. long l = 1;
  830. if (matches.Count > 0 && matches[0].Groups.Count > 0)
  831. {
  832. try
  833. {
  834. long.TryParse(matches[0].Groups[1].Value, out l);
  835. retval = l;
  836. worked = true;
  837. }
  838. catch (FormatException fe)
  839. {
  840. m_log.Warn(
  841. "[AUTO BACKUP]: Error: Can't parse long value from file name to determine next OAR backup file number!",
  842. fe);
  843. subtract++;
  844. }
  845. }
  846. else
  847. {
  848. subtract++;
  849. }
  850. }
  851. }
  852. return retval;
  853. }
  854. }
  855. }