From 638c3d25b0787c2fbdabbe80519389ad8ddb944d Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 8 Aug 2013 00:48:22 +0100 Subject: [PATCH 01/20] Remove never implemented stub modules commands (list, load, unload) from back in 2009. "show modules" is the functional console command that will show currently loaded modules. Addresses http://opensimulator.org/mantis/view.php?id=6730 --- OpenSim/Region/Application/OpenSim.cs | 40 --------------------------- 1 file changed, 40 deletions(-) diff --git a/OpenSim/Region/Application/OpenSim.cs b/OpenSim/Region/Application/OpenSim.cs index 9dcc8dac22..58f936813a 100644 --- a/OpenSim/Region/Application/OpenSim.cs +++ b/OpenSim/Region/Application/OpenSim.cs @@ -348,18 +348,6 @@ namespace OpenSim m_console.Commands.AddCommand("Regions", false, "delete-region", "delete-region ", "Delete a region from disk", RunCommand); - - m_console.Commands.AddCommand("General", false, "modules list", - "modules list", - "List modules", HandleModules); - - m_console.Commands.AddCommand("General", false, "modules load", - "modules load ", - "Load a module", HandleModules); - - m_console.Commands.AddCommand("General", false, "modules unload", - "modules unload ", - "Unload a module", HandleModules); } protected override void ShutdownSpecific() @@ -556,34 +544,6 @@ namespace OpenSim regInfo.EstateSettings.Save(); } - /// - /// Load, Unload, and list Region modules in use - /// - /// - /// - private void HandleModules(string module, string[] cmd) - { - List args = new List(cmd); - args.RemoveAt(0); - string[] cmdparams = args.ToArray(); - - if (cmdparams.Length > 0) - { - switch (cmdparams[0].ToLower()) - { - case "list": - //TODO: Convert to new region modules - break; - case "unload": - //TODO: Convert to new region modules - break; - case "load": - //TODO: Convert to new region modules - break; - } - } - } - /// /// Runs commands issued by the server console from the operator /// From e4da8d74d8b0e04a5439638c8868367d4b20050f Mon Sep 17 00:00:00 2001 From: Kevin Cozens Date: Mon, 5 Aug 2013 19:28:11 -0400 Subject: [PATCH 02/20] Additional regression tests for the location routines in Location.cs --- OpenSim/Framework/Tests/LocationTest.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/OpenSim/Framework/Tests/LocationTest.cs b/OpenSim/Framework/Tests/LocationTest.cs index af5f164d7f..3d5d1d275b 100644 --- a/OpenSim/Framework/Tests/LocationTest.cs +++ b/OpenSim/Framework/Tests/LocationTest.cs @@ -55,11 +55,18 @@ namespace OpenSim.Framework.Tests Location TestLocation2 = new Location(1095216660736000); Assert.That(TestLocation1 == TestLocation2); + Assert.That(TestLocation1.X == 255000 && TestLocation1.Y == 256000, "Test xy location doesn't match position in the constructor"); Assert.That(TestLocation2.X == 255000 && TestLocation2.Y == 256000, "Test xy location doesn't match regionhandle provided"); Assert.That(TestLocation2.RegionHandle == 1095216660736000, "Location RegionHandle Property didn't match regionhandle provided in constructor"); + ulong RegionHandle = TestLocation1.RegionHandle; + Assert.That(RegionHandle.Equals(1095216660736000), "Equals(regionhandle) failed to match the position in the constructor"); + + TestLocation2 = new Location(RegionHandle); + Assert.That(TestLocation2.Equals(255000, 256000), "Decoded regionhandle failed to match the original position in the constructor"); + TestLocation1 = new Location(255001, 256001); TestLocation2 = new Location(1095216660736000); From 43da879ea2123707190875fe2615e01be19ecced Mon Sep 17 00:00:00 2001 From: Kevin Cozens Date: Mon, 5 Aug 2013 19:29:38 -0400 Subject: [PATCH 03/20] Added regression tests for the routines related to fake parcel IDs. --- OpenSim/Framework/Tests/UtilTest.cs | 84 +++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/OpenSim/Framework/Tests/UtilTest.cs b/OpenSim/Framework/Tests/UtilTest.cs index 11ca0683d3..c83651c60c 100644 --- a/OpenSim/Framework/Tests/UtilTest.cs +++ b/OpenSim/Framework/Tests/UtilTest.cs @@ -282,5 +282,89 @@ namespace OpenSim.Framework.Tests String.Format("Incorrect InventoryType mapped from Content-Type {0}", invcontenttypes[i])); } } + + [Test] + public void FakeParcelIDTests() + { + byte[] hexBytes8 = { 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10 }; + byte[] hexBytes16 = { + 0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, + 0x77, 0x69, 0x5a, 0x4b, 0x3c, 0x2d, 0x1e, 0x0f }; + UInt64 var64Bit = (UInt64)0xfedcba9876543210; + + //Region handle is for location 255000,256000. + ulong regionHandle1 = 1095216660736000; + uint x1 = 100; + uint y1 = 200; + uint z1 = 22; + ulong regionHandle2; + uint x2, y2, z2; + UUID fakeParcelID1, fakeParcelID2, uuid; + + ulong bigInt64 = Util.BytesToUInt64Big(hexBytes8); + Assert.AreEqual(var64Bit, bigInt64, + "BytesToUint64Bit conversion of 8 bytes to UInt64 failed."); + + //Test building and decoding using some typical input values + fakeParcelID1 = Util.BuildFakeParcelID(regionHandle1, x1, y1); + Util.ParseFakeParcelID(fakeParcelID1, out regionHandle2, out x2, out y2); + Assert.AreEqual(regionHandle1, regionHandle2, + "region handle decoded from FakeParcelID wth X/Y failed."); + Assert.AreEqual(x1, x2, + "X coordinate decoded from FakeParcelID wth X/Y failed."); + Assert.AreEqual(y1, y2, + "Y coordinate decoded from FakeParcelID wth X/Y failed."); + + fakeParcelID1 = Util.BuildFakeParcelID(regionHandle1, x1, y1, z1); + Util.ParseFakeParcelID(fakeParcelID1, out regionHandle2, out x2, out y2, out z2); + Assert.AreEqual(regionHandle1, regionHandle2, + "region handle decoded from FakeParcelID with X/Y/Z failed."); + Assert.AreEqual(x1, x2, + "X coordinate decoded from FakeParcelID with X/Y/Z failed."); + Assert.AreEqual(y1, y2, + "Y coordinate decoded from FakeParcelID with X/Y/Z failed."); + Assert.AreEqual(z1, z2, + "Z coordinate decoded from FakeParcelID with X/Y/Z failed."); + + //Do some more extreme tests to check the encoding and decoding + x1 = 0x55aa; + y1 = 0x9966; + z1 = 0x5a96; + + fakeParcelID1 = Util.BuildFakeParcelID(var64Bit, x1, y1); + Util.ParseFakeParcelID(fakeParcelID1, out regionHandle2, out x2, out y2); + Assert.AreEqual(var64Bit, regionHandle2, + "region handle decoded from FakeParcelID with X/Y/Z failed."); + Assert.AreEqual(x1, x2, + "X coordinate decoded from FakeParcelID with X/Y/Z failed."); + Assert.AreEqual(y1, y2, + "Y coordinate decoded from FakeParcelID with X/Y/Z failed."); + + fakeParcelID1 = Util.BuildFakeParcelID(var64Bit, x1, y1, z1); + Util.ParseFakeParcelID(fakeParcelID1, out regionHandle2, out x2, out y2, out z2); + Assert.AreEqual(var64Bit, regionHandle2, + "region handle decoded from FakeParcelID with X/Y/Z failed."); + Assert.AreEqual(x1, x2, + "X coordinate decoded from FakeParcelID with X/Y/Z failed."); + Assert.AreEqual(y1, y2, + "Y coordinate decoded from FakeParcelID with X/Y/Z failed."); + Assert.AreEqual(z1, z2, + "Z coordinate decoded from FakeParcelID with X/Y/Z failed."); + + + x1 = 64; + y1 = 192; + fakeParcelID1 = Util.BuildFakeParcelID(regionHandle1, x1, y1); + Util.FakeParcelIDToGlobalPosition(fakeParcelID1, out x2, out y2); + Assert.AreEqual(255000+x1, x2, + "Global X coordinate decoded from regionHandle failed."); + Assert.AreEqual(256000+y1, y2, + "Global Y coordinate decoded from regionHandle failed."); + + uuid = new UUID("00dd0700-00d1-0700-3800-000032000000"); + Util.FakeParcelIDToGlobalPosition(uuid, out x2, out y2); +System.Console.WriteLine("uuid: " + uuid); +System.Console.WriteLine("x2/y2: " + x2 + "," + y2); + } } } From 64216b34a49377f6999f6d2cf624d3c537d3f9d5 Mon Sep 17 00:00:00 2001 From: Kevin Cozens Date: Mon, 5 Aug 2013 19:30:46 -0400 Subject: [PATCH 04/20] Fixed error in BuildFakeParcelID() which was detected by regression tests. --- OpenSim/Framework/Util.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenSim/Framework/Util.cs b/OpenSim/Framework/Util.cs index 6a15734d5a..f0e5bc1d13 100644 --- a/OpenSim/Framework/Util.cs +++ b/OpenSim/Framework/Util.cs @@ -1247,7 +1247,7 @@ namespace OpenSim.Framework byte[] bytes = { (byte)regionHandle, (byte)(regionHandle >> 8), (byte)(regionHandle >> 16), (byte)(regionHandle >> 24), - (byte)(regionHandle >> 32), (byte)(regionHandle >> 40), (byte)(regionHandle >> 48), (byte)(regionHandle << 56), + (byte)(regionHandle >> 32), (byte)(regionHandle >> 40), (byte)(regionHandle >> 48), (byte)(regionHandle >> 56), (byte)x, (byte)(x >> 8), 0, 0, (byte)y, (byte)(y >> 8), 0, 0 }; return new UUID(bytes, 0); @@ -1258,7 +1258,7 @@ namespace OpenSim.Framework byte[] bytes = { (byte)regionHandle, (byte)(regionHandle >> 8), (byte)(regionHandle >> 16), (byte)(regionHandle >> 24), - (byte)(regionHandle >> 32), (byte)(regionHandle >> 40), (byte)(regionHandle >> 48), (byte)(regionHandle << 56), + (byte)(regionHandle >> 32), (byte)(regionHandle >> 40), (byte)(regionHandle >> 48), (byte)(regionHandle >> 56), (byte)x, (byte)(x >> 8), (byte)z, (byte)(z >> 8), (byte)y, (byte)(y >> 8), 0, 0 }; return new UUID(bytes, 0); From ce1361f2fea9c650cc774941b5cd95b14b8f01f5 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 8 Aug 2013 01:07:30 +0100 Subject: [PATCH 05/20] minor: Remove console lines at bottom of FakeParcelIDTests() regression test that were accidentally left in --- OpenSim/Framework/Tests/UtilTest.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/OpenSim/Framework/Tests/UtilTest.cs b/OpenSim/Framework/Tests/UtilTest.cs index c83651c60c..3b7f25299f 100644 --- a/OpenSim/Framework/Tests/UtilTest.cs +++ b/OpenSim/Framework/Tests/UtilTest.cs @@ -363,8 +363,6 @@ namespace OpenSim.Framework.Tests uuid = new UUID("00dd0700-00d1-0700-3800-000032000000"); Util.FakeParcelIDToGlobalPosition(uuid, out x2, out y2); -System.Console.WriteLine("uuid: " + uuid); -System.Console.WriteLine("x2/y2: " + x2 + "," + y2); } } } From 99a4a914887c16483074b0145b9b6da765ac024a Mon Sep 17 00:00:00 2001 From: teravus Date: Wed, 7 Aug 2013 21:22:04 -0500 Subject: [PATCH 06/20] * This makes in-world terrain editing smoother, even in MegaRegions. This change only affects the editing user's experience. Non-editing users will see nothing different from the current 'slow' result. See comments for the thought process and how the issues surrounding terrain editing, cache, bandwidth, threading, terrain patch reliability and throttling were balanced. --- .../ClientStack/Linden/UDP/LLClientView.cs | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs b/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs index 8b2440a63f..0dbce2fdd5 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs @@ -327,7 +327,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP private PriorityQueue m_entityProps; private Prioritizer m_prioritizer; private bool m_disableFacelights = false; - + private volatile bool m_justEditedTerrain = false; /// /// List used in construction of data blocks for an object update packet. This is to stop us having to /// continually recreate it. @@ -1239,9 +1239,32 @@ namespace OpenSim.Region.ClientStack.LindenUDP LLHeightFieldMoronize(map); LayerDataPacket layerpack = TerrainCompressor.CreateLandPacket(heightmap, patches); - layerpack.Header.Reliable = true; + + // When a user edits the terrain, so much data is sent, the data queues up fast and presents a sub optimal editing experience. + // To alleviate this issue, when the user edits the terrain, we start skipping the queues until they're done editing the terrain. + // We also make them unreliable because it's extremely likely that multiple packets will be sent for a terrain patch area + // invalidating previous packets for that area. - OutPacket(layerpack, ThrottleOutPacketType.Land); + // It's possible for an editing user to flood themselves with edited packets but the majority of use cases are such that only a + // tiny percentage of users will be editing the terrain. Other, non-editing users will see the edits much slower. + + // One last note on this topic, by the time users are going to be editing the terrain, it's extremely likely that the sim will + // have rezzed already and therefore this is not likely going to cause any additional issues with lost packets, objects or terrain + // patches. + + // m_justEditedTerrain is volatile, so test once and duplicate two affected statements so we only have one cache miss. + if (m_justEditedTerrain) + { + layerpack.Header.Reliable = false; + OutPacket(layerpack, + ThrottleOutPacketType.Unknown ); + } + else + { + layerpack.Header.Reliable = true; + OutPacket(layerpack, + ThrottleOutPacketType.Land); + } } catch (Exception e) { @@ -6263,6 +6286,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP //m_log.Info("[LAND]: LAND:" + modify.ToString()); if (modify.ParcelData.Length > 0) { + // Note: the ModifyTerrain event handler sends out updated packets before the end of this event. Therefore, + // a simple boolean value should work and perhaps queue up just a few terrain patch packets at the end of the edit. + m_justEditedTerrain = true; // Prevent terrain packet (Land layer) from being queued, make it unreliable if (OnModifyTerrain != null) { for (int i = 0; i < modify.ParcelData.Length; i++) @@ -6278,6 +6304,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP } } } + m_justEditedTerrain = false; // Queue terrain packet (Land layer) if necessary, make it reliable again } return true; From 4e86674a3a671561c3a9c85925308f2004fcc922 Mon Sep 17 00:00:00 2001 From: teravus Date: Wed, 7 Aug 2013 23:33:23 -0500 Subject: [PATCH 07/20] * Added set water height [] [] console command following the set terrain heights console command as an example. --- .../World/Estate/EstateManagementCommands.cs | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/OpenSim/Region/CoreModules/World/Estate/EstateManagementCommands.cs b/OpenSim/Region/CoreModules/World/Estate/EstateManagementCommands.cs index 4d49794347..173b603fe5 100644 --- a/OpenSim/Region/CoreModules/World/Estate/EstateManagementCommands.cs +++ b/OpenSim/Region/CoreModules/World/Estate/EstateManagementCommands.cs @@ -76,6 +76,13 @@ namespace OpenSim.Region.CoreModules.World.Estate " that coordinate. Corner # SW = 0, NW = 1, SE = 2, NE = 3, all corners = -1.", consoleSetTerrainHeights); + m_module.Scene.AddCommand("Regions", m_module, "set water height", + "set water height [] []", + "Sets the water height in meters. If and are specified, it will only set it on regions with a matching coordinate. " + + "Specify -1 in or to wildcard that coordinate.", + consoleSetWaterHeight); + + m_module.Scene.AddCommand( "Estates", m_module, "estate show", "estate show", "Shows all estates on the simulator.", ShowEstatesCommand); } @@ -121,7 +128,29 @@ namespace OpenSim.Region.CoreModules.World.Estate } } } - + protected void consoleSetWaterHeight(string module, string[] args) + { + string heightstring = args[3]; + + int x = (args.Length > 4 ? int.Parse(args[4]) : -1); + int y = (args.Length > 5 ? int.Parse(args[5]) : -1); + + if (x == -1 || m_module.Scene.RegionInfo.RegionLocX == x) + { + if (y == -1 || m_module.Scene.RegionInfo.RegionLocY == y) + { + double selectedheight = double.Parse(heightstring); + + m_log.Debug("[ESTATEMODULE]: Setting water height in " + m_module.Scene.RegionInfo.RegionName + " to " + + string.Format(" {0}", selectedheight)); + m_module.Scene.RegionInfo.RegionSettings.WaterHeight = selectedheight; + + m_module.Scene.RegionInfo.RegionSettings.Save(); + m_module.TriggerRegionInfoChange(); + m_module.sendRegionHandshakeToAll(); + } + } + } protected void consoleSetTerrainHeights(string module, string[] args) { string num = args[3]; From 50c163ae6ca734610694f4edcc109ff0bdc65ba1 Mon Sep 17 00:00:00 2001 From: Robert Adams Date: Tue, 6 Aug 2013 08:21:16 -0700 Subject: [PATCH 08/20] Add a JSON web fetch of the statististics managed by StatsManager. Disabled by default. Enable by setting [Startup]ManagedStatsRemoteFetchURI="Something" and thereafter "http://ServerHTTPPort/Something/" will return all the managed stats (equivilent to "show stats all" console command). Accepts queries "cat=", "cont=" and "stat=" to specify statistic category, container and statistic names. The special name "all" is the default and returns all values in that group. --- OpenSim/Framework/Monitoring/StatsManager.cs | 31 ++++++++++++++++++++ OpenSim/Region/Application/OpenSim.cs | 7 +++++ OpenSim/Region/Application/OpenSimBase.cs | 2 ++ 3 files changed, 40 insertions(+) diff --git a/OpenSim/Framework/Monitoring/StatsManager.cs b/OpenSim/Framework/Monitoring/StatsManager.cs index c8e838cadf..23c6f18be8 100644 --- a/OpenSim/Framework/Monitoring/StatsManager.cs +++ b/OpenSim/Framework/Monitoring/StatsManager.cs @@ -26,10 +26,12 @@ */ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; +using OpenSim.Framework; using OpenMetaverse.StructuredData; namespace OpenSim.Framework.Monitoring @@ -262,6 +264,35 @@ namespace OpenSim.Framework.Monitoring return map; } + public static Hashtable HandleStatsRequest(Hashtable request) + { + Hashtable responsedata = new Hashtable(); + string regpath = request["uri"].ToString(); + int response_code = 200; + string contenttype = "text/json"; + + string pCategoryName = StatsManager.AllSubCommand; + string pContainerName = StatsManager.AllSubCommand; + string pStatName = StatsManager.AllSubCommand; + + if (request.ContainsKey("cat")) pCategoryName = request["cat"].ToString(); + if (request.ContainsKey("cont")) pContainerName = request["cat"].ToString(); + if (request.ContainsKey("stat")) pStatName = request["cat"].ToString(); + + string strOut = StatsManager.GetStatsAsOSDMap(pCategoryName, pContainerName, pStatName).ToString(); + + // m_log.DebugFormat("{0} StatFetch: uri={1}, cat={2}, cont={3}, stat={4}, resp={5}", + // LogHeader, regpath, pCategoryName, pContainerName, pStatName, strOut); + + responsedata["int_response_code"] = response_code; + responsedata["content_type"] = contenttype; + responsedata["keepalive"] = false; + responsedata["str_response_string"] = strOut; + responsedata["access_control_allow_origin"] = "*"; + + return responsedata; + } + // /// // /// Start collecting statistics related to assets. // /// Should only be called once. diff --git a/OpenSim/Region/Application/OpenSim.cs b/OpenSim/Region/Application/OpenSim.cs index 58f936813a..13fdb3b82c 100644 --- a/OpenSim/Region/Application/OpenSim.cs +++ b/OpenSim/Region/Application/OpenSim.cs @@ -172,6 +172,13 @@ namespace OpenSim if (userStatsURI != String.Empty) MainServer.Instance.AddStreamHandler(new OpenSim.UXSimStatusHandler(this)); + if (managedStatsURI != String.Empty) + { + string urlBase = String.Format("/{0}/", managedStatsURI); + MainServer.Instance.AddHTTPHandler(urlBase, StatsManager.HandleStatsRequest); + m_log.WarnFormat("[OPENSIM] Enabling remote managed stats fetch. URL = {0}", urlBase); + } + if (m_console is RemoteConsole) { if (m_consolePort == 0) diff --git a/OpenSim/Region/Application/OpenSimBase.cs b/OpenSim/Region/Application/OpenSimBase.cs index f0c088a52b..b032e7fe97 100644 --- a/OpenSim/Region/Application/OpenSimBase.cs +++ b/OpenSim/Region/Application/OpenSimBase.cs @@ -75,6 +75,7 @@ namespace OpenSim protected int proxyOffset = 0; public string userStatsURI = String.Empty; + public string managedStatsURI = String.Empty; protected bool m_autoCreateClientStack = true; @@ -188,6 +189,7 @@ namespace OpenSim CreatePIDFile(pidFile); userStatsURI = startupConfig.GetString("Stats_URI", String.Empty); + managedStatsURI = startupConfig.GetString("ManagedStatsRemoteFetchURI", String.Empty); } // Load the simulation data service From c67c55e0fcd93e5b68e61e5f1bc4341af48568d3 Mon Sep 17 00:00:00 2001 From: Robert Adams Date: Thu, 8 Aug 2013 08:56:22 -0700 Subject: [PATCH 09/20] Better error reporting when registering LSL function extensions (comms module). For unknown reasons, a dynamic function signature cannot have more than 5 parameters. Error message now tells you this fact so you can curse MS and then go change your function definitions. --- .../ScriptModuleCommsModule.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/OpenSim/Region/CoreModules/Scripting/ScriptModuleComms/ScriptModuleCommsModule.cs b/OpenSim/Region/CoreModules/Scripting/ScriptModuleComms/ScriptModuleCommsModule.cs index a515346260..6da22228d9 100644 --- a/OpenSim/Region/CoreModules/Scripting/ScriptModuleComms/ScriptModuleCommsModule.cs +++ b/OpenSim/Region/CoreModules/Scripting/ScriptModuleComms/ScriptModuleCommsModule.cs @@ -45,6 +45,7 @@ namespace OpenSim.Region.CoreModules.Scripting.ScriptModuleComms { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private static string LogHeader = "[MODULE COMMS]"; private Dictionary m_constants = new Dictionary(); @@ -148,7 +149,7 @@ namespace OpenSim.Region.CoreModules.Scripting.ScriptModuleComms MethodInfo mi = GetMethodInfoFromType(target.GetType(), meth, true); if (mi == null) { - m_log.WarnFormat("[MODULE COMMANDS] Failed to register method {0}", meth); + m_log.WarnFormat("{0} Failed to register method {1}", LogHeader, meth); return; } @@ -165,7 +166,7 @@ namespace OpenSim.Region.CoreModules.Scripting.ScriptModuleComms { // m_log.DebugFormat("[MODULE COMMANDS] Register method {0} from type {1}", mi.Name, (target is Type) ? ((Type)target).Name : target.GetType().Name); - Type delegateType; + Type delegateType = typeof(void); List typeArgs = mi.GetParameters() .Select(p => p.ParameterType) .ToList(); @@ -176,8 +177,16 @@ namespace OpenSim.Region.CoreModules.Scripting.ScriptModuleComms } else { - typeArgs.Add(mi.ReturnType); - delegateType = Expression.GetFuncType(typeArgs.ToArray()); + try + { + typeArgs.Add(mi.ReturnType); + delegateType = Expression.GetFuncType(typeArgs.ToArray()); + } + catch (Exception e) + { + m_log.ErrorFormat("{0} Failed to create function signature. Most likely more than 5 parameters. Method={1}. Error={2}", + LogHeader, mi.Name, e); + } } Delegate fcall; From d9bd6e6b5be3100141a3b1202f859c65a302d4ee Mon Sep 17 00:00:00 2001 From: Robert Adams Date: Thu, 8 Aug 2013 09:41:11 -0700 Subject: [PATCH 10/20] Add parameter and explanation of ManagedStats return to OpenSimDefaults.ini. Add 'callback' query parameter to managed stats return to return function form of JSON data. --- OpenSim/Framework/Monitoring/StatsManager.cs | 6 ++++++ OpenSim/Region/Application/OpenSim.cs | 4 ++-- bin/OpenSimDefaults.ini | 7 +++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/OpenSim/Framework/Monitoring/StatsManager.cs b/OpenSim/Framework/Monitoring/StatsManager.cs index 23c6f18be8..7cf1fa7458 100644 --- a/OpenSim/Framework/Monitoring/StatsManager.cs +++ b/OpenSim/Framework/Monitoring/StatsManager.cs @@ -281,6 +281,12 @@ namespace OpenSim.Framework.Monitoring string strOut = StatsManager.GetStatsAsOSDMap(pCategoryName, pContainerName, pStatName).ToString(); + // If requestor wants it as a callback function, build response as a function rather than just the JSON string. + if (request.ContainsKey("callback")) + { + strOut = request["callback"].ToString() + "(" + strOut + ");"; + } + // m_log.DebugFormat("{0} StatFetch: uri={1}, cat={2}, cont={3}, stat={4}, resp={5}", // LogHeader, regpath, pCategoryName, pContainerName, pStatName, strOut); diff --git a/OpenSim/Region/Application/OpenSim.cs b/OpenSim/Region/Application/OpenSim.cs index 13fdb3b82c..1cdd8683c0 100644 --- a/OpenSim/Region/Application/OpenSim.cs +++ b/OpenSim/Region/Application/OpenSim.cs @@ -175,8 +175,8 @@ namespace OpenSim if (managedStatsURI != String.Empty) { string urlBase = String.Format("/{0}/", managedStatsURI); - MainServer.Instance.AddHTTPHandler(urlBase, StatsManager.HandleStatsRequest); - m_log.WarnFormat("[OPENSIM] Enabling remote managed stats fetch. URL = {0}", urlBase); + MainServer.Instance.AddHTTPHandler(urlBase, StatsManager.HandleStatsRequest); + m_log.InfoFormat("[OPENSIM] Enabling remote managed stats fetch. URL = {0}", urlBase); } if (m_console is RemoteConsole) diff --git a/bin/OpenSimDefaults.ini b/bin/OpenSimDefaults.ini index d5d29ec674..0a85085872 100644 --- a/bin/OpenSimDefaults.ini +++ b/bin/OpenSimDefaults.ini @@ -264,8 +264,15 @@ ; Simulator Stats URI ; Enable JSON simulator data by setting a URI name (case sensitive) + ; Returns regular sim stats (SimFPS, ...) ; Stats_URI = "jsonSimStats" + ; Simulator StatsManager URI + ; Enable fetch of StatsManager registered stats. Fetch is query which can optionally + ; specify category, container and stat to fetch. If not selected, returns all of that type. + ; http://simulatorHTTPport/ManagedStats/?cat=Category&cont=Container&stat=Statistic + ; ManagedStatsRemoteFetchURI = "ManagedStats" + ; Make OpenSim start all regions woth logins disabled. They will need ; to be enabled from the console if this is set ; StartDisabled = false From 9fc97cbbf77f269d69aaa6234dd6e12ebf3b9563 Mon Sep 17 00:00:00 2001 From: Dan Lake Date: Thu, 8 Aug 2013 12:44:03 -0700 Subject: [PATCH 11/20] Make m_originRegionID in ScenePresence public to allow DSG module to work for now. Once the code churn on teleport ends, I can find a better solution --- OpenSim/Region/Framework/Scenes/ScenePresence.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenSim/Region/Framework/Scenes/ScenePresence.cs b/OpenSim/Region/Framework/Scenes/ScenePresence.cs index 7fd13027aa..1b8c2760ca 100644 --- a/OpenSim/Region/Framework/Scenes/ScenePresence.cs +++ b/OpenSim/Region/Framework/Scenes/ScenePresence.cs @@ -290,9 +290,9 @@ namespace OpenSim.Region.Framework.Scenes /// /// In the V1 teleport protocol, the destination simulator sends ReleaseAgent to this address. /// - string m_callbackURI; + private string m_callbackURI; - UUID m_originRegionID; + public UUID m_originRegionID; /// /// Used by the entity transfer module to signal when the presence should not be closed because a subsequent From 6410a25cefcb1e7f87b76f273f8c6569fbe17670 Mon Sep 17 00:00:00 2001 From: Robert Adams Date: Thu, 8 Aug 2013 13:53:12 -0700 Subject: [PATCH 12/20] BulletSim: adjust avatar position when the avatar's size is changed. This fixes the problem of avatars bouncing when logged in. Added a little height to the avatar height fudges to eliminate a problem of feet being in the ground a bit. --- OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs | 12 ++++++++++-- OpenSim/Region/Physics/BulletSPlugin/BSParam.cs | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs b/OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs index 58a417ed8f..9af3dcea48 100644 --- a/OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSCharacter.cs @@ -96,8 +96,8 @@ public sealed class BSCharacter : BSPhysObject m_moveActor = new BSActorAvatarMove(PhysScene, this, AvatarMoveActorName); PhysicalActors.Add(AvatarMoveActorName, m_moveActor); - DetailLog("{0},BSCharacter.create,call,size={1},scale={2},density={3},volume={4},mass={5}", - LocalID, _size, Scale, Density, _avatarVolume, RawMass); + DetailLog("{0},BSCharacter.create,call,size={1},scale={2},density={3},volume={4},mass={5},pos={6}", + LocalID, _size, Scale, Density, _avatarVolume, RawMass, pos); // do actual creation in taint time PhysScene.TaintedObject("BSCharacter.create", delegate() @@ -190,6 +190,10 @@ public sealed class BSCharacter : BSPhysObject } set { + // This is how much the avatar size is changing. Positive means getting bigger. + // The avatar altitude must be adjusted for this change. + float heightChange = value.Z - _size.Z; + _size = value; // Old versions of ScenePresence passed only the height. If width and/or depth are zero, // replace with the default values. @@ -207,6 +211,10 @@ public sealed class BSCharacter : BSPhysObject { PhysScene.PE.SetLocalScaling(PhysShape.physShapeInfo, Scale); UpdatePhysicalMassProperties(RawMass, true); + + // Adjust the avatar's position to account for the increase/decrease in size + ForcePosition = new OMV.Vector3(RawPosition.X, RawPosition.Y, RawPosition.Z + heightChange / 2f); + // Make sure this change appears as a property update event PhysScene.PE.PushUpdate(PhysBody); } diff --git a/OpenSim/Region/Physics/BulletSPlugin/BSParam.cs b/OpenSim/Region/Physics/BulletSPlugin/BSParam.cs index 452017177f..fcb892a4f1 100755 --- a/OpenSim/Region/Physics/BulletSPlugin/BSParam.cs +++ b/OpenSim/Region/Physics/BulletSPlugin/BSParam.cs @@ -570,9 +570,9 @@ public static class BSParam new ParameterDefn("AvatarHeightLowFudge", "A fudge factor to make small avatars stand on the ground", -0.2f ), new ParameterDefn("AvatarHeightMidFudge", "A fudge distance to adjust average sized avatars to be standing on ground", - 0.1f ), + 0.2f ), new ParameterDefn("AvatarHeightHighFudge", "A fudge factor to make tall avatars stand on the ground", - 0.1f ), + 0.2f ), new ParameterDefn("AvatarContactProcessingThreshold", "Distance from capsule to check for collisions", 0.1f ), new ParameterDefn("AvatarBelowGroundUpCorrectionMeters", "Meters to move avatar up if it seems to be below ground", From b1c26a56b3d615f3709363e3a2f91b5423f5891f Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 8 Aug 2013 23:29:30 +0100 Subject: [PATCH 13/20] Fix an issue where under teleport v2 protocol, teleporting from regions in an line from A->B->C would not close region A when reaching C The root cause was that v2 was only closing neighbour agents if the root connection also needed a close. However, fixing this requires the neighbour regions also detect when they should not close due to re-teleports re-establishing the child connection. This involves restructuring the code to introduce a scene presence state machine that can serialize the different add and remove client calls that are now possible with the late close of the This commit appears to fix these issues and improve teleport, but still has holes on at least quick reteleporting (and possibly occasionally on ordinary teleports). Also, has not been completely tested yet in scenarios where regions are running on different simulators --- .../Caps/EventQueue/Tests/EventQueueTests.cs | 2 +- .../ClientStack/Linden/UDP/LLClientView.cs | 6 + .../ClientStack/Linden/UDP/LLUDPServer.cs | 6 +- .../Linden/UDP/Tests/BasicCircuitTests.cs | 2 +- .../EntityTransfer/EntityTransferModule.cs | 48 +-- .../World/Estate/EstateManagementModule.cs | 6 +- OpenSim/Region/Framework/Scenes/Scene.cs | 310 +++++++++++------- .../Region/Framework/Scenes/ScenePresence.cs | 36 +- 8 files changed, 268 insertions(+), 148 deletions(-) diff --git a/OpenSim/Region/ClientStack/Linden/Caps/EventQueue/Tests/EventQueueTests.cs b/OpenSim/Region/ClientStack/Linden/Caps/EventQueue/Tests/EventQueueTests.cs index 141af8a937..626932f53d 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/EventQueue/Tests/EventQueueTests.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/EventQueue/Tests/EventQueueTests.cs @@ -91,7 +91,7 @@ namespace OpenSim.Region.ClientStack.Linden.Tests public void RemoveForClient() { TestHelpers.InMethod(); -// log4net.Config.XmlConfigurator.Configure(); +// TestHelpers.EnableLogging(); UUID spId = TestHelpers.ParseTail(0x1); diff --git a/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs b/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs index 8b2440a63f..e775a81275 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/LLClientView.cs @@ -512,7 +512,13 @@ namespace OpenSim.Region.ClientStack.LindenUDP // We still perform a force close inside the sync lock since this is intended to attempt close where // there is some unidentified connection problem, not where we have issues due to deadlock if (!IsActive && !force) + { + m_log.DebugFormat( + "[CLIENT]: Not attempting to close inactive client {0} in {1} since force flag is not set", + Name, m_scene.Name); + return; + } IsActive = false; CloseWithoutChecks(); diff --git a/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs index 5c38399c08..de2f9d4e17 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/LLUDPServer.cs @@ -1799,9 +1799,9 @@ namespace OpenSim.Region.ClientStack.LindenUDP if (!client.SceneAgent.IsChildAgent) client.Kick("Simulator logged you out due to connection timeout."); - - client.CloseWithoutChecks(); } + + m_scene.IncomingCloseAgent(client.AgentId, true); } private void IncomingPacketHandler() @@ -2142,7 +2142,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP if (!client.IsLoggingOut) { client.IsLoggingOut = true; - client.Close(); + m_scene.IncomingCloseAgent(client.AgentId, false); } } } diff --git a/OpenSim/Region/ClientStack/Linden/UDP/Tests/BasicCircuitTests.cs b/OpenSim/Region/ClientStack/Linden/UDP/Tests/BasicCircuitTests.cs index b47ff54efe..97002244fc 100644 --- a/OpenSim/Region/ClientStack/Linden/UDP/Tests/BasicCircuitTests.cs +++ b/OpenSim/Region/ClientStack/Linden/UDP/Tests/BasicCircuitTests.cs @@ -200,7 +200,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP.Tests public void TestLogoutClientDueToAck() { TestHelpers.InMethod(); -// TestHelpers.EnableLogging(); + TestHelpers.EnableLogging(); IniConfigSource ics = new IniConfigSource(); IConfig config = ics.AddConfig("ClientStack.LindenUDP"); diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs index 01ef710387..2f7425310a 100644 --- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs @@ -1064,8 +1064,12 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // Now let's make it officially a child agent sp.MakeChildAgent(); - // Finally, let's close this previously-known-as-root agent, when the jump is outside the view zone + // May still need to signal neighbours whether child agents may need closing irrespective of whether this + // one needed closing. Neighbour regions also contain logic to prevent a close if a subsequent move or + // teleport re-established the child connection. + sp.CloseChildAgents(newRegionX, newRegionY); + // Finally, let's close this previously-known-as-root agent, when the jump is outside the view zone if (NeedsClosing(sp.DrawDistance, oldRegionX, newRegionX, oldRegionY, newRegionY, reg)) { sp.DoNotCloseAfterTeleport = false; @@ -1081,14 +1085,12 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer if (!sp.DoNotCloseAfterTeleport) { // OK, it got this agent. Let's close everything - m_log.DebugFormat("[ENTITY TRANSFER MODULE]: Closing in agent {0} in region {1}", sp.Name, Scene.Name); - sp.CloseChildAgents(newRegionX, newRegionY); + m_log.DebugFormat("[ENTITY TRANSFER MODULE]: Closing agent {0} in {1}", sp.Name, Scene.Name); sp.Scene.IncomingCloseAgent(sp.UUID, false); - } else { - m_log.DebugFormat("[ENTITY TRANSFER MODULE]: Not closing agent {0}, user is back in {0}", sp.Name, Scene.Name); + m_log.DebugFormat("[ENTITY TRANSFER MODULE]: Not closing agent {0}, user is back in {1}", sp.Name, Scene.Name); sp.DoNotCloseAfterTeleport = false; } } @@ -1863,10 +1865,10 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer List newRegions = NewNeighbours(neighbourHandles, previousRegionNeighbourHandles); List oldRegions = OldNeighbours(neighbourHandles, previousRegionNeighbourHandles); - //Dump("Current Neighbors", neighbourHandles); - //Dump("Previous Neighbours", previousRegionNeighbourHandles); - //Dump("New Neighbours", newRegions); - //Dump("Old Neighbours", oldRegions); + Dump("Current Neighbors", neighbourHandles); + Dump("Previous Neighbours", previousRegionNeighbourHandles); + Dump("New Neighbours", newRegions); + Dump("Old Neighbours", oldRegions); /// Update the scene presence's known regions here on this region sp.DropOldNeighbours(oldRegions); @@ -1874,8 +1876,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer /// Collect as many seeds as possible Dictionary seeds; if (sp.Scene.CapsModule != null) - seeds - = new Dictionary(sp.Scene.CapsModule.GetChildrenSeeds(sp.UUID)); + seeds = new Dictionary(sp.Scene.CapsModule.GetChildrenSeeds(sp.UUID)); else seeds = new Dictionary(); @@ -1945,6 +1946,7 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer newAgent = true; else newAgent = false; +// continue; if (neighbour.RegionHandle != sp.Scene.RegionInfo.RegionHandle) { @@ -2178,18 +2180,18 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer return handles; } -// private void Dump(string msg, List handles) -// { -// m_log.InfoFormat("-------------- HANDLE DUMP ({0}) ---------", msg); -// foreach (ulong handle in handles) -// { -// uint x, y; -// Utils.LongToUInts(handle, out x, out y); -// x = x / Constants.RegionSize; -// y = y / Constants.RegionSize; -// m_log.InfoFormat("({0}, {1})", x, y); -// } -// } + private void Dump(string msg, List handles) + { + m_log.InfoFormat("-------------- HANDLE DUMP ({0}) ---------", msg); + foreach (ulong handle in handles) + { + uint x, y; + Utils.LongToUInts(handle, out x, out y); + x = x / Constants.RegionSize; + y = y / Constants.RegionSize; + m_log.InfoFormat("({0}, {1})", x, y); + } + } #endregion diff --git a/OpenSim/Region/CoreModules/World/Estate/EstateManagementModule.cs b/OpenSim/Region/CoreModules/World/Estate/EstateManagementModule.cs index 121b2aa8e5..31547a69cb 100644 --- a/OpenSim/Region/CoreModules/World/Estate/EstateManagementModule.cs +++ b/OpenSim/Region/CoreModules/World/Estate/EstateManagementModule.cs @@ -562,7 +562,7 @@ namespace OpenSim.Region.CoreModules.World.Estate if (!Scene.TeleportClientHome(user, s.ControllingClient)) { s.ControllingClient.Kick("Your access to the region was revoked and TP home failed - you have been logged out."); - s.ControllingClient.Close(); + Scene.IncomingCloseAgent(s.UUID, false); } } } @@ -797,7 +797,7 @@ namespace OpenSim.Region.CoreModules.World.Estate if (!Scene.TeleportClientHome(prey, s.ControllingClient)) { s.ControllingClient.Kick("You were teleported home by the region owner, but the TP failed - you have been logged out."); - s.ControllingClient.Close(); + Scene.IncomingCloseAgent(s.UUID, false); } } } @@ -820,7 +820,7 @@ namespace OpenSim.Region.CoreModules.World.Estate if (!Scene.TeleportClientHome(p.UUID, p.ControllingClient)) { p.ControllingClient.Kick("You were teleported home by the region owner, but the TP failed - you have been logged out."); - p.ControllingClient.Close(); + Scene.IncomingCloseAgent(p.UUID, false); } } } diff --git a/OpenSim/Region/Framework/Scenes/Scene.cs b/OpenSim/Region/Framework/Scenes/Scene.cs index 56cd57e9df..5f10869072 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.cs @@ -151,7 +151,7 @@ namespace OpenSim.Region.Framework.Scenes public SynchronizeSceneHandler SynchronizeScene; /// - /// Used to prevent simultaneous calls to RemoveClient() for the same agent from interfering with each other. + /// Used to prevent simultaneous calls to code that adds and removes agents. /// private object m_removeClientLock = new object(); @@ -1312,7 +1312,7 @@ namespace OpenSim.Region.Framework.Scenes Thread.Sleep(500); // Stop all client threads. - ForEachScenePresence(delegate(ScenePresence avatar) { avatar.ControllingClient.Close(); }); + ForEachScenePresence(delegate(ScenePresence avatar) { IncomingCloseAgent(avatar.UUID, false); }); m_log.Debug("[SCENE]: Persisting changed objects"); EventManager.TriggerSceneShuttingDown(this); @@ -2972,7 +2972,7 @@ namespace OpenSim.Region.Framework.Scenes { PresenceService.LogoutAgent(sp.ControllingClient.SessionId); - sp.ControllingClient.Close(); + IncomingCloseAgent(sp.UUID, false); } else { @@ -3384,47 +3384,48 @@ namespace OpenSim.Region.Framework.Scenes public override void RemoveClient(UUID agentID, bool closeChildAgents) { -// CheckHeartbeat(); - bool isChildAgent = false; - AgentCircuitData acd; + AgentCircuitData acd = m_authenticateHandler.GetAgentCircuitData(agentID); - lock (m_removeClientLock) + // Shouldn't be necessary since RemoveClient() is currently only called by IClientAPI.Close() which + // in turn is only called by Scene.IncomingCloseAgent() which checks whether the presence exists or not + // However, will keep for now just in case. + if (acd == null) { - acd = m_authenticateHandler.GetAgentCircuitData(agentID); + m_log.ErrorFormat( + "[SCENE]: No agent circuit found for {0} in {1}, aborting Scene.RemoveClient", agentID, Name); - if (acd == null) - { - m_log.ErrorFormat("[SCENE]: No agent circuit found for {0}, aborting Scene.RemoveClient", agentID); - return; - } - else - { - // We remove the acd up here to avoid later race conditions if two RemoveClient() calls occurred - // simultaneously. - // We also need to remove by agent ID since NPCs will have no circuit code. - m_authenticateHandler.RemoveCircuit(agentID); - } + return; + } + else + { + m_authenticateHandler.RemoveCircuit(agentID); } + // TODO: Can we now remove this lock? lock (acd) - { + { + bool isChildAgent = false; + ScenePresence avatar = GetScenePresence(agentID); - + + // Shouldn't be necessary since RemoveClient() is currently only called by IClientAPI.Close() which + // in turn is only called by Scene.IncomingCloseAgent() which checks whether the presence exists or not + // However, will keep for now just in case. if (avatar == null) { - m_log.WarnFormat( + m_log.ErrorFormat( "[SCENE]: Called RemoveClient() with agent ID {0} but no such presence is in the scene.", agentID); return; } - + try { isChildAgent = avatar.IsChildAgent; m_log.DebugFormat( "[SCENE]: Removing {0} agent {1} {2} from {3}", - (isChildAgent ? "child" : "root"), avatar.Name, agentID, RegionInfo.RegionName); + isChildAgent ? "child" : "root", avatar.Name, agentID, Name); // Don't do this to root agents, it's not nice for the viewer if (closeChildAgents && isChildAgent) @@ -3587,13 +3588,13 @@ namespace OpenSim.Region.Framework.Scenes /// is activated later when the viewer sends the initial UseCircuitCodePacket UDP packet (in the case of /// the LLUDP stack). /// - /// CircuitData of the agent who is connecting + /// CircuitData of the agent who is connecting /// Outputs the reason for the false response on this string /// True for normal presence. False for NPC /// or other applications where a full grid/Hypergrid presence may not be required. /// True if the region accepts this agent. False if it does not. False will /// also return a reason. - public bool NewUserConnection(AgentCircuitData agent, uint teleportFlags, out string reason, bool requirePresenceLookup) + public bool NewUserConnection(AgentCircuitData acd, uint teleportFlags, out string reason, bool requirePresenceLookup) { bool vialogin = ((teleportFlags & (uint)TPFlags.ViaLogin) != 0 || (teleportFlags & (uint)TPFlags.ViaHGLogin) != 0); @@ -3613,15 +3614,15 @@ namespace OpenSim.Region.Framework.Scenes m_log.DebugFormat( "[SCENE]: Region {0} told of incoming {1} agent {2} {3} {4} (circuit code {5}, IP {6}, viewer {7}, teleportflags ({8}), position {9})", RegionInfo.RegionName, - (agent.child ? "child" : "root"), - agent.firstname, - agent.lastname, - agent.AgentID, - agent.circuitcode, - agent.IPAddress, - agent.Viewer, + (acd.child ? "child" : "root"), + acd.firstname, + acd.lastname, + acd.AgentID, + acd.circuitcode, + acd.IPAddress, + acd.Viewer, ((TPFlags)teleportFlags).ToString(), - agent.startpos + acd.startpos ); if (!LoginsEnabled) @@ -3639,7 +3640,7 @@ namespace OpenSim.Region.Framework.Scenes { foreach (string viewer in m_AllowedViewers) { - if (viewer == agent.Viewer.Substring(0, viewer.Length).Trim().ToLower()) + if (viewer == acd.Viewer.Substring(0, viewer.Length).Trim().ToLower()) { ViewerDenied = false; break; @@ -3656,7 +3657,7 @@ namespace OpenSim.Region.Framework.Scenes { foreach (string viewer in m_BannedViewers) { - if (viewer == agent.Viewer.Substring(0, viewer.Length).Trim().ToLower()) + if (viewer == acd.Viewer.Substring(0, viewer.Length).Trim().ToLower()) { ViewerDenied = true; break; @@ -3668,61 +3669,104 @@ namespace OpenSim.Region.Framework.Scenes { m_log.DebugFormat( "[SCENE]: Access denied for {0} {1} using {2}", - agent.firstname, agent.lastname, agent.Viewer); + acd.firstname, acd.lastname, acd.Viewer); reason = "Access denied, your viewer is banned by the region owner"; return false; } ILandObject land; + ScenePresence sp; - lock (agent) + lock (m_removeClientLock) { - ScenePresence sp = GetScenePresence(agent.AgentID); - - if (sp != null) + sp = GetScenePresence(acd.AgentID); + + // We need to ensure that we are not already removing the scene presence before we ask it not to be + // closed. + if (sp != null && sp.IsChildAgent && sp.LifecycleState == ScenePresenceState.Running) { - if (!sp.IsChildAgent) - { - // We have a root agent. Is it in transit? - if (!EntityTransferModule.IsInTransit(sp.UUID)) - { - // We have a zombie from a crashed session. - // Or the same user is trying to be root twice here, won't work. - // Kill it. - m_log.WarnFormat( - "[SCENE]: Existing root scene presence detected for {0} {1} in {2} when connecting. Removing existing presence.", - sp.Name, sp.UUID, RegionInfo.RegionName); + m_log.DebugFormat( + "[SCENE]: Reusing existing child scene presence for {0} in {1}", sp.Name, Name); - if (sp.ControllingClient != null) - sp.ControllingClient.Close(true); - - sp = null; - } - //else - // m_log.WarnFormat("[SCENE]: Existing root scene presence for {0} {1} in {2}, but agent is in trasit", sp.Name, sp.UUID, RegionInfo.RegionName); - } - else + // In the case where, for example, an A B C D region layout, an avatar may + // teleport from A -> D, but then -> C before A has asked B to close its old child agent. When C + // renews the lease on the child agent at B, we must make sure that the close from A does not succeed. + if (!acd.ChildrenCapSeeds.ContainsKey(RegionInfo.RegionHandle)) { - // We have a child agent here + m_log.DebugFormat( + "[SCENE]: Setting DoNotCloseAfterTeleport for child scene presence {0} in {1} because source will attempt close.", + sp.Name, Name); + sp.DoNotCloseAfterTeleport = true; - //m_log.WarnFormat("[SCENE]: Existing child scene presence for {0} {1} in {2}", sp.Name, sp.UUID, RegionInfo.RegionName); } } + } + + // Need to poll here in case we are currently deleting an sp. Letting threads run over each other will + // allow unpredictable things to happen. + if (sp != null) + { + const int polls = 10; + const int pollInterval = 1000; + int pollsLeft = polls; + + while (sp.LifecycleState == ScenePresenceState.Removing && pollsLeft-- > 0) + Thread.Sleep(pollInterval); + + if (sp.LifecycleState == ScenePresenceState.Removing) + { + m_log.WarnFormat( + "[SCENE]: Agent {0} in {1} was still being removed after {2}s. Aborting NewUserConnection.", + sp.Name, Name, polls * pollInterval / 1000); + + return false; + } + else if (polls != pollsLeft) + { + m_log.DebugFormat( + "[SCENE]: NewUserConnection for agent {0} in {1} had to wait {2}s for in-progress removal to complete on an old presence.", + sp.Name, Name, polls * pollInterval / 1000); + } + } + + // TODO: can we remove this lock? + lock (acd) + { + if (sp != null && !sp.IsChildAgent) + { + // We have a root agent. Is it in transit? + if (!EntityTransferModule.IsInTransit(sp.UUID)) + { + // We have a zombie from a crashed session. + // Or the same user is trying to be root twice here, won't work. + // Kill it. + m_log.WarnFormat( + "[SCENE]: Existing root scene presence detected for {0} {1} in {2} when connecting. Removing existing presence.", + sp.Name, sp.UUID, RegionInfo.RegionName); + + if (sp.ControllingClient != null) + IncomingCloseAgent(sp.UUID, true); + + sp = null; + } + //else + // m_log.WarnFormat("[SCENE]: Existing root scene presence for {0} {1} in {2}, but agent is in trasit", sp.Name, sp.UUID, RegionInfo.RegionName); + } // Optimistic: add or update the circuit data with the new agent circuit data and teleport flags. // We need the circuit data here for some of the subsequent checks. (groups, for example) // If the checks fail, we remove the circuit. - agent.teleportFlags = teleportFlags; - m_authenticateHandler.AddNewCircuit(agent.circuitcode, agent); + acd.teleportFlags = teleportFlags; + m_authenticateHandler.AddNewCircuit(acd.circuitcode, acd); - land = LandChannel.GetLandObject(agent.startpos.X, agent.startpos.Y); + land = LandChannel.GetLandObject(acd.startpos.X, acd.startpos.Y); // On login test land permisions if (vialogin) { - if (land != null && !TestLandRestrictions(agent.AgentID, out reason, ref agent.startpos.X, ref agent.startpos.Y)) + if (land != null && !TestLandRestrictions(acd.AgentID, out reason, ref acd.startpos.X, ref acd.startpos.Y)) { - m_authenticateHandler.RemoveCircuit(agent.circuitcode); + m_authenticateHandler.RemoveCircuit(acd.circuitcode); return false; } } @@ -3733,9 +3777,9 @@ namespace OpenSim.Region.Framework.Scenes { try { - if (!VerifyUserPresence(agent, out reason)) + if (!VerifyUserPresence(acd, out reason)) { - m_authenticateHandler.RemoveCircuit(agent.circuitcode); + m_authenticateHandler.RemoveCircuit(acd.circuitcode); return false; } } @@ -3744,16 +3788,16 @@ namespace OpenSim.Region.Framework.Scenes m_log.ErrorFormat( "[SCENE]: Exception verifying presence {0}{1}", e.Message, e.StackTrace); - m_authenticateHandler.RemoveCircuit(agent.circuitcode); + m_authenticateHandler.RemoveCircuit(acd.circuitcode); return false; } } try { - if (!AuthorizeUser(agent, SeeIntoRegion, out reason)) + if (!AuthorizeUser(acd, SeeIntoRegion, out reason)) { - m_authenticateHandler.RemoveCircuit(agent.circuitcode); + m_authenticateHandler.RemoveCircuit(acd.circuitcode); return false; } } @@ -3762,19 +3806,19 @@ namespace OpenSim.Region.Framework.Scenes m_log.ErrorFormat( "[SCENE]: Exception authorizing user {0}{1}", e.Message, e.StackTrace); - m_authenticateHandler.RemoveCircuit(agent.circuitcode); + m_authenticateHandler.RemoveCircuit(acd.circuitcode); return false; } m_log.InfoFormat( "[SCENE]: Region {0} authenticated and authorized incoming {1} agent {2} {3} {4} (circuit code {5})", - RegionInfo.RegionName, (agent.child ? "child" : "root"), agent.firstname, agent.lastname, - agent.AgentID, agent.circuitcode); + Name, (acd.child ? "child" : "root"), acd.firstname, acd.lastname, + acd.AgentID, acd.circuitcode); if (CapsModule != null) { - CapsModule.SetAgentCapsSeeds(agent); - CapsModule.CreateCaps(agent.AgentID); + CapsModule.SetAgentCapsSeeds(acd); + CapsModule.CreateCaps(acd.AgentID); } } else @@ -3787,14 +3831,14 @@ namespace OpenSim.Region.Framework.Scenes { m_log.DebugFormat( "[SCENE]: Adjusting known seeds for existing agent {0} in {1}", - agent.AgentID, RegionInfo.RegionName); + acd.AgentID, RegionInfo.RegionName); sp.AdjustKnownSeeds(); if (CapsModule != null) { - CapsModule.SetAgentCapsSeeds(agent); - CapsModule.CreateCaps(agent.AgentID); + CapsModule.SetAgentCapsSeeds(acd); + CapsModule.CreateCaps(acd.AgentID); } } } @@ -3802,23 +3846,23 @@ namespace OpenSim.Region.Framework.Scenes // Try caching an incoming user name much earlier on to see if this helps with an issue // where HG users are occasionally seen by others as "Unknown User" because their UUIDName // request for the HG avatar appears to trigger before the user name is cached. - CacheUserName(null, agent); + CacheUserName(null, acd); } if (vialogin) { // CleanDroppedAttachments(); - if (TestBorderCross(agent.startpos, Cardinals.E)) + if (TestBorderCross(acd.startpos, Cardinals.E)) { - Border crossedBorder = GetCrossedBorder(agent.startpos, Cardinals.E); - agent.startpos.X = crossedBorder.BorderLine.Z - 1; + Border crossedBorder = GetCrossedBorder(acd.startpos, Cardinals.E); + acd.startpos.X = crossedBorder.BorderLine.Z - 1; } - if (TestBorderCross(agent.startpos, Cardinals.N)) + if (TestBorderCross(acd.startpos, Cardinals.N)) { - Border crossedBorder = GetCrossedBorder(agent.startpos, Cardinals.N); - agent.startpos.Y = crossedBorder.BorderLine.Z - 1; + Border crossedBorder = GetCrossedBorder(acd.startpos, Cardinals.N); + acd.startpos.Y = crossedBorder.BorderLine.Z - 1; } //Mitigate http://opensimulator.org/mantis/view.php?id=3522 @@ -3828,39 +3872,39 @@ namespace OpenSim.Region.Framework.Scenes { lock (EastBorders) { - if (agent.startpos.X > EastBorders[0].BorderLine.Z) + if (acd.startpos.X > EastBorders[0].BorderLine.Z) { m_log.Warn("FIX AGENT POSITION"); - agent.startpos.X = EastBorders[0].BorderLine.Z * 0.5f; - if (agent.startpos.Z > 720) - agent.startpos.Z = 720; + acd.startpos.X = EastBorders[0].BorderLine.Z * 0.5f; + if (acd.startpos.Z > 720) + acd.startpos.Z = 720; } } lock (NorthBorders) { - if (agent.startpos.Y > NorthBorders[0].BorderLine.Z) + if (acd.startpos.Y > NorthBorders[0].BorderLine.Z) { m_log.Warn("FIX Agent POSITION"); - agent.startpos.Y = NorthBorders[0].BorderLine.Z * 0.5f; - if (agent.startpos.Z > 720) - agent.startpos.Z = 720; + acd.startpos.Y = NorthBorders[0].BorderLine.Z * 0.5f; + if (acd.startpos.Z > 720) + acd.startpos.Z = 720; } } } else { - if (agent.startpos.X > EastBorders[0].BorderLine.Z) + if (acd.startpos.X > EastBorders[0].BorderLine.Z) { m_log.Warn("FIX AGENT POSITION"); - agent.startpos.X = EastBorders[0].BorderLine.Z * 0.5f; - if (agent.startpos.Z > 720) - agent.startpos.Z = 720; + acd.startpos.X = EastBorders[0].BorderLine.Z * 0.5f; + if (acd.startpos.Z > 720) + acd.startpos.Z = 720; } - if (agent.startpos.Y > NorthBorders[0].BorderLine.Z) + if (acd.startpos.Y > NorthBorders[0].BorderLine.Z) { m_log.Warn("FIX Agent POSITION"); - agent.startpos.Y = NorthBorders[0].BorderLine.Z * 0.5f; - if (agent.startpos.Z > 720) - agent.startpos.Z = 720; + acd.startpos.Y = NorthBorders[0].BorderLine.Z * 0.5f; + if (acd.startpos.Z > 720) + acd.startpos.Z = 720; } } @@ -3876,12 +3920,12 @@ namespace OpenSim.Region.Framework.Scenes { // We have multiple SpawnPoints, Route the agent to a random or sequential one if (SpawnPointRouting == "random") - agent.startpos = spawnpoints[Util.RandomClass.Next(spawnpoints.Count) - 1].GetLocation( + acd.startpos = spawnpoints[Util.RandomClass.Next(spawnpoints.Count) - 1].GetLocation( telehub.AbsolutePosition, telehub.GroupRotation ); else - agent.startpos = spawnpoints[SpawnPoint()].GetLocation( + acd.startpos = spawnpoints[SpawnPoint()].GetLocation( telehub.AbsolutePosition, telehub.GroupRotation ); @@ -3889,7 +3933,7 @@ namespace OpenSim.Region.Framework.Scenes else { // We have a single SpawnPoint and will route the agent to it - agent.startpos = spawnpoints[0].GetLocation(telehub.AbsolutePosition, telehub.GroupRotation); + acd.startpos = spawnpoints[0].GetLocation(telehub.AbsolutePosition, telehub.GroupRotation); } return true; @@ -3900,7 +3944,7 @@ namespace OpenSim.Region.Framework.Scenes { if (land.LandData.LandingType == (byte)1 && land.LandData.UserLocation != Vector3.Zero) { - agent.startpos = land.LandData.UserLocation; + acd.startpos = land.LandData.UserLocation; } } } @@ -4359,11 +4403,51 @@ namespace OpenSim.Region.Framework.Scenes /// public bool IncomingCloseAgent(UUID agentID, bool force) { - //m_log.DebugFormat("[SCENE]: Processing incoming close agent for {0}", agentID); - ScenePresence presence = m_sceneGraph.GetScenePresence(agentID); - if (presence != null) + ScenePresence sp; + + lock (m_removeClientLock) { - presence.ControllingClient.Close(force); + sp = GetScenePresence(agentID); + + if (sp == null) + { + m_log.DebugFormat( + "[SCENE]: Called RemoveClient() with agent ID {0} but no such presence is in {1}", + agentID, Name); + + return false; + } + + if (sp.LifecycleState != ScenePresenceState.Running) + { + m_log.DebugFormat( + "[SCENE]: Called RemoveClient() for {0} in {1} but presence is already in state {2}", + sp.Name, Name, sp.LifecycleState); + + return false; + } + + // We need to avoid a race condition where in, for example, an A B C D region layout, an avatar may + // teleport from A -> D, but then -> C before A has asked B to close its old child agent. We do not + // want to obey this close since C may have renewed the child agent lease on B. + if (sp.DoNotCloseAfterTeleport) + { + m_log.DebugFormat( + "[SCENE]: Not closing {0} agent {1} in {2} since another simulator has re-established the child connection", + sp.IsChildAgent ? "child" : "root", sp.Name, Name); + + // Need to reset the flag so that a subsequent close after another teleport can succeed. + sp.DoNotCloseAfterTeleport = false; + + return false; + } + + sp.LifecycleState = ScenePresenceState.Removing; + } + + if (sp != null) + { + sp.ControllingClient.Close(force); return true; } diff --git a/OpenSim/Region/Framework/Scenes/ScenePresence.cs b/OpenSim/Region/Framework/Scenes/ScenePresence.cs index 7fd13027aa..bdcdf03911 100644 --- a/OpenSim/Region/Framework/Scenes/ScenePresence.cs +++ b/OpenSim/Region/Framework/Scenes/ScenePresence.cs @@ -74,6 +74,8 @@ namespace OpenSim.Region.Framework.Scenes public class ScenePresence : EntityBase, IScenePresence { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + // ~ScenePresence() // { // m_log.DebugFormat("[SCENE PRESENCE]: Destructor called on {0}", Name); @@ -85,10 +87,27 @@ namespace OpenSim.Region.Framework.Scenes m_scene.EventManager.TriggerScenePresenceUpdated(this); } - private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - public PresenceType PresenceType { get; private set; } + private ScenePresenceStateMachine m_stateMachine; + + /// + /// The current state of this presence. Governs only the existence lifecycle. See ScenePresenceStateMachine + /// for more details. + /// + public ScenePresenceState LifecycleState + { + get + { + return m_stateMachine.GetState(); + } + + set + { + m_stateMachine.SetState(value); + } + } + // private static readonly byte[] DEFAULT_TEXTURE = AvatarAppearance.GetDefaultTexture().GetBytes(); private static readonly Array DIR_CONTROL_FLAGS = Enum.GetValues(typeof(Dir_ControlFlags)); private static readonly Vector3 HEAD_ADJUSTMENT = new Vector3(0f, 0f, 0.3f); @@ -766,7 +785,7 @@ namespace OpenSim.Region.Framework.Scenes public ScenePresence( IClientAPI client, Scene world, AvatarAppearance appearance, PresenceType type) - { + { AttachmentsSyncLock = new Object(); AllowMovement = true; IsChildAgent = true; @@ -811,6 +830,8 @@ namespace OpenSim.Region.Framework.Scenes SetDirectionVectors(); Appearance = appearance; + + m_stateMachine = new ScenePresenceStateMachine(this); } public void RegisterToEvents() @@ -879,7 +900,7 @@ namespace OpenSim.Region.Framework.Scenes /// public void MakeRootAgent(Vector3 pos, bool isFlying) { - m_log.DebugFormat( + m_log.InfoFormat( "[SCENE]: Upgrading child to root agent for {0} in {1}", Name, m_scene.RegionInfo.RegionName); @@ -887,6 +908,11 @@ namespace OpenSim.Region.Framework.Scenes IsChildAgent = false; + // Must reset this here so that a teleport to a region next to an existing region does not keep the flag + // set and prevent the close of the connection on a subsequent re-teleport. + // Should not be needed if we are not trying to tell this region to close +// DoNotCloseAfterTeleport = false; + IGroupsModule gm = m_scene.RequestModuleInterface(); if (gm != null) Grouptitle = gm.GetGroupTitle(m_uuid); @@ -3738,6 +3764,8 @@ namespace OpenSim.Region.Framework.Scenes // m_reprioritizationTimer.Dispose(); RemoveFromPhysicalScene(); + + LifecycleState = ScenePresenceState.Removed; } public void AddAttachment(SceneObjectGroup gobj) From 99bce9d87723af1958a76cf8ac5e765ca804549d Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 9 Aug 2013 00:24:22 +0100 Subject: [PATCH 14/20] Fix an issue with an A->C->B->A teleport where these regions are in a row (A,B,C) where the A root agent is still closed, terminating the connection. This was occuring because teleport to B did not set DoNotCloseAfterTeleport on A as it was a neighbour (where it isn't set to avoid the issue where the source region doesn't send Close() to regions that are still neighbours (hence not resetting DoNotCloseAfterTeleport). Fix here is to still set DoNotCloseAfterTeleport if scene presence is still registered as in transit from A --- OpenSim/Region/Framework/Scenes/Scene.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/OpenSim/Region/Framework/Scenes/Scene.cs b/OpenSim/Region/Framework/Scenes/Scene.cs index 5f10869072..a3ea7d94fd 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.cs @@ -3699,6 +3699,14 @@ namespace OpenSim.Region.Framework.Scenes sp.DoNotCloseAfterTeleport = true; } + else if (EntityTransferModule.IsInTransit(sp.UUID)) + { + m_log.DebugFormat( + "[SCENE]: Setting DoNotCloseAfterTeleport for child scene presence {0} in {1} because this region will attempt previous end-of-teleport close.", + sp.Name, Name); + + sp.DoNotCloseAfterTeleport = true; + } } } From 7e01213bf2dba1f771cc49b3d27ad97e93c35961 Mon Sep 17 00:00:00 2001 From: Diva Canto Date: Fri, 9 Aug 2013 08:31:15 -0700 Subject: [PATCH 15/20] Go easy on enforcing session ids in position updates --- OpenSim/Region/Framework/Scenes/Scene.cs | 33 ++++++++++++------------ 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/OpenSim/Region/Framework/Scenes/Scene.cs b/OpenSim/Region/Framework/Scenes/Scene.cs index 503b81af53..1633e07d0e 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.cs @@ -4267,24 +4267,25 @@ namespace OpenSim.Region.Framework.Scenes ScenePresence childAgentUpdate = GetScenePresence(cAgentData.AgentID); if (childAgentUpdate != null) { - if (childAgentUpdate.ControllingClient.SessionId == cAgentData.SessionID) + if (childAgentUpdate.ControllingClient.SessionId != cAgentData.SessionID) + // Only warn for now + m_log.WarnFormat("[SCENE]: Attempt at updating position of agent {0} with invalid session id {1}. Neighbor running older version?", + childAgentUpdate.UUID, cAgentData.SessionID); + + // I can't imagine *yet* why we would get an update if the agent is a root agent.. + // however to avoid a race condition crossing borders.. + if (childAgentUpdate.IsChildAgent) { - // I can't imagine *yet* why we would get an update if the agent is a root agent.. - // however to avoid a race condition crossing borders.. - if (childAgentUpdate.IsChildAgent) - { - uint rRegionX = (uint)(cAgentData.RegionHandle >> 40); - uint rRegionY = (((uint)(cAgentData.RegionHandle)) >> 8); - uint tRegionX = RegionInfo.RegionLocX; - uint tRegionY = RegionInfo.RegionLocY; - //Send Data to ScenePresence - childAgentUpdate.ChildAgentDataUpdate(cAgentData, tRegionX, tRegionY, rRegionX, rRegionY); - // Not Implemented: - //TODO: Do we need to pass the message on to one of our neighbors? - } + uint rRegionX = (uint)(cAgentData.RegionHandle >> 40); + uint rRegionY = (((uint)(cAgentData.RegionHandle)) >> 8); + uint tRegionX = RegionInfo.RegionLocX; + uint tRegionY = RegionInfo.RegionLocY; + //Send Data to ScenePresence + childAgentUpdate.ChildAgentDataUpdate(cAgentData, tRegionX, tRegionY, rRegionX, rRegionY); + // Not Implemented: + //TODO: Do we need to pass the message on to one of our neighbors? } - else - m_log.WarnFormat("[SCENE]: Attempt at updating position of agent {0} with invalid session id {1}", childAgentUpdate.UUID, cAgentData.SessionID); + return true; } From 6fcbf219da1d5095d6f37e8e44031db9ef108c39 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 9 Aug 2013 17:48:35 +0100 Subject: [PATCH 16/20] Comment back out seed dump code enabled in b1c26a56. Also adds a few teleport comments. --- .../EntityTransfer/EntityTransferModule.cs | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs index 2f7425310a..87f0264f04 100644 --- a/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs +++ b/OpenSim/Region/CoreModules/Framework/EntityTransfer/EntityTransferModule.cs @@ -1067,6 +1067,12 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer // May still need to signal neighbours whether child agents may need closing irrespective of whether this // one needed closing. Neighbour regions also contain logic to prevent a close if a subsequent move or // teleport re-established the child connection. + // + // It may be possible to also close child agents after a pause but one needs to be very careful about + // race conditions between different regions on rapid teleporting (e.g. from A1 to a non-neighbour B, back + // to a neighbour A2 then off to a non-neighbour C. Also, closing child agents early may be more compatible + // with complicated scenarios where there a mixture of V1 and V2 teleports, though this is conjecture. It's + // easier to close immediately and greatly reduce the scope of race conditions if possible. sp.CloseChildAgents(newRegionX, newRegionY); // Finally, let's close this previously-known-as-root agent, when the jump is outside the view zone @@ -1865,10 +1871,10 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer List newRegions = NewNeighbours(neighbourHandles, previousRegionNeighbourHandles); List oldRegions = OldNeighbours(neighbourHandles, previousRegionNeighbourHandles); - Dump("Current Neighbors", neighbourHandles); - Dump("Previous Neighbours", previousRegionNeighbourHandles); - Dump("New Neighbours", newRegions); - Dump("Old Neighbours", oldRegions); +// Dump("Current Neighbors", neighbourHandles); +// Dump("Previous Neighbours", previousRegionNeighbourHandles); +// Dump("New Neighbours", newRegions); +// Dump("Old Neighbours", oldRegions); /// Update the scene presence's known regions here on this region sp.DropOldNeighbours(oldRegions); @@ -2180,18 +2186,18 @@ namespace OpenSim.Region.CoreModules.Framework.EntityTransfer return handles; } - private void Dump(string msg, List handles) - { - m_log.InfoFormat("-------------- HANDLE DUMP ({0}) ---------", msg); - foreach (ulong handle in handles) - { - uint x, y; - Utils.LongToUInts(handle, out x, out y); - x = x / Constants.RegionSize; - y = y / Constants.RegionSize; - m_log.InfoFormat("({0}, {1})", x, y); - } - } +// private void Dump(string msg, List handles) +// { +// m_log.InfoFormat("-------------- HANDLE DUMP ({0}) ---------", msg); +// foreach (ulong handle in handles) +// { +// uint x, y; +// Utils.LongToUInts(handle, out x, out y); +// x = x / Constants.RegionSize; +// y = y / Constants.RegionSize; +// m_log.InfoFormat("({0}, {1})", x, y); +// } +// } #endregion From aec701972844d4e006bb5d50e7983b818fb39249 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 9 Aug 2013 17:57:24 +0100 Subject: [PATCH 17/20] Add missing file from b1c26a56 --- .../Scenes/ScenePresenceStateMachine.cs | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 OpenSim/Region/Framework/Scenes/ScenePresenceStateMachine.cs diff --git a/OpenSim/Region/Framework/Scenes/ScenePresenceStateMachine.cs b/OpenSim/Region/Framework/Scenes/ScenePresenceStateMachine.cs new file mode 100644 index 0000000000..dc3a212b65 --- /dev/null +++ b/OpenSim/Region/Framework/Scenes/ScenePresenceStateMachine.cs @@ -0,0 +1,102 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; + +namespace OpenSim.Region.Framework.Scenes +{ + /// + /// The possible states that a scene presence can be in. This is currently orthagonal to whether a scene presence + /// is root or child. + /// + /// + /// This is a state machine. + /// + /// [Entry] => Running + /// Running => Removing + /// Removing => Removed + /// + /// All other methods should only see the scene presence in running state - this is the normal operational state + /// Removed state occurs when the presence has been removed. This is the end state with no exit. + /// + public enum ScenePresenceState + { + Running, // Normal operation state. The scene presence is available. + Removing, // The presence is in the process of being removed from the scene via Scene.RemoveClient. + Removed, // The presence has been removed from the scene and is effectively dead. + // There is no exit from this state. + } + + internal class ScenePresenceStateMachine + { + private ScenePresence m_sp; + private ScenePresenceState m_state; + + internal ScenePresenceStateMachine(ScenePresence sp) + { + m_sp = sp; + m_state = ScenePresenceState.Running; + } + + internal ScenePresenceState GetState() + { + return m_state; + } + + /// + /// Updates the state of an agent that is already in transit. + /// + /// + /// + /// + /// Illegal transitions will throw an Exception + internal void SetState(ScenePresenceState newState) + { + bool transitionOkay = false; + + lock (this) + { + if (newState == ScenePresenceState.Removing && m_state == ScenePresenceState.Running) + transitionOkay = true; + else if (newState == ScenePresenceState.Removed && m_state == ScenePresenceState.Removing) + transitionOkay = true; + } + + if (!transitionOkay) + { + throw new Exception( + string.Format( + "Scene presence {0} is not allowed to move from state {1} to new state {2} in {3}", + m_sp.Name, m_state, newState, m_sp.Scene.Name)); + } + else + { + m_state = newState; + } + } + } +} \ No newline at end of file From bfdcdbb2f3f568c3ef829f30b8326dbc8a835f03 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 9 Aug 2013 17:59:58 +0100 Subject: [PATCH 18/20] Increase wait for source region to sent UpdateAgent to 10 seconds instead of 4. This is giving much better results on teleports between simulators over my lan where for some reason there is a pause before the receiving simulator processes UpdateAgent() At this point, v2 teleports between neighbour and non-neighbour regions on a single simulator and between v2 simulators and between a v1 and v2 simulator are working okay for me in different scenarios (e.g. simple teleport, teleport back to original quickly and re-teleport, teleport back to neighbour and re-teleport. etc.) --- OpenSim/Region/Framework/Scenes/ScenePresence.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSim/Region/Framework/Scenes/ScenePresence.cs b/OpenSim/Region/Framework/Scenes/ScenePresence.cs index aac80f7adb..69339b784e 100644 --- a/OpenSim/Region/Framework/Scenes/ScenePresence.cs +++ b/OpenSim/Region/Framework/Scenes/ScenePresence.cs @@ -1355,7 +1355,7 @@ namespace OpenSim.Region.Framework.Scenes private bool WaitForUpdateAgent(IClientAPI client) { // Before UpdateAgent, m_originRegionID is UUID.Zero; after, it's non-Zero - int count = 20; + int count = 50; while (m_originRegionID.Equals(UUID.Zero) && count-- > 0) { m_log.DebugFormat("[SCENE PRESENCE]: Agent {0} waiting for update in {1}", client.Name, Scene.Name); From 23ca1f859e20e537572cb774943784d50d3cacb8 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 9 Aug 2013 18:27:26 +0100 Subject: [PATCH 19/20] minor: Consistently log IOCP for IO completion thread startup log information instead of mixing this with "IO Completion Threads" --- OpenSim/Region/Application/Application.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenSim/Region/Application/Application.cs b/OpenSim/Region/Application/Application.cs index 2e155ec9dc..3a4e5df48c 100644 --- a/OpenSim/Region/Application/Application.cs +++ b/OpenSim/Region/Application/Application.cs @@ -142,19 +142,19 @@ namespace OpenSim if (iocpThreads < iocpThreadsMin) { iocpThreads = iocpThreadsMin; - m_log.InfoFormat("[OPENSIM MAIN]: Bumping up max IO completion threads to {0}",iocpThreads); + m_log.InfoFormat("[OPENSIM MAIN]: Bumping up max IOCP threads to {0}",iocpThreads); } // Make sure we don't overallocate IOCP threads and thrash system resources if ( iocpThreads > iocpThreadsMax ) { iocpThreads = iocpThreadsMax; - m_log.InfoFormat("[OPENSIM MAIN]: Limiting max IO completion threads to {0}",iocpThreads); + m_log.InfoFormat("[OPENSIM MAIN]: Limiting max IOCP completion threads to {0}",iocpThreads); } // set the resulting worker and IO completion thread counts back to ThreadPool if ( System.Threading.ThreadPool.SetMaxThreads(workerThreads, iocpThreads) ) { m_log.InfoFormat( - "[OPENSIM MAIN]: Threadpool set to {0} max worker threads and {1} max IO completion threads", + "[OPENSIM MAIN]: Threadpool set to {0} max worker threads and {1} max IOCP threads", workerThreads, iocpThreads); } else From 216f5afe54576c4852974b8479ac95654dc9e08e Mon Sep 17 00:00:00 2001 From: Robert Adams Date: Sat, 10 Aug 2013 09:09:52 -0700 Subject: [PATCH 20/20] Stats treaking. Update ToOSDMap for Stat and PercentageStat to return all the various numbers that have been added to the console output. Break out EventHistogram from CounterStat. --- .../Framework/Monitoring/Stats/CounterStat.cs | 150 +-------------- .../Monitoring/Stats/EventHistogram.cs | 173 ++++++++++++++++++ .../Monitoring/Stats/PercentageStat.cs | 16 ++ OpenSim/Framework/Monitoring/Stats/Stat.cs | 46 ++++- 4 files changed, 235 insertions(+), 150 deletions(-) create mode 100755 OpenSim/Framework/Monitoring/Stats/EventHistogram.cs diff --git a/OpenSim/Framework/Monitoring/Stats/CounterStat.cs b/OpenSim/Framework/Monitoring/Stats/CounterStat.cs index 04442c35b1..318cf1cb9c 100755 --- a/OpenSim/Framework/Monitoring/Stats/CounterStat.cs +++ b/OpenSim/Framework/Monitoring/Stats/CounterStat.cs @@ -34,142 +34,6 @@ using OpenMetaverse.StructuredData; namespace OpenSim.Framework.Monitoring { -// Create a time histogram of events. The histogram is built in a wrap-around -// array of equally distributed buckets. -// For instance, a minute long histogram of second sized buckets would be: -// new EventHistogram(60, 1000) -public class EventHistogram -{ - private int m_timeBase; - private int m_numBuckets; - private int m_bucketMilliseconds; - private int m_lastBucket; - private int m_totalHistogramMilliseconds; - private long[] m_histogram; - private object histoLock = new object(); - - public EventHistogram(int numberOfBuckets, int millisecondsPerBucket) - { - m_numBuckets = numberOfBuckets; - m_bucketMilliseconds = millisecondsPerBucket; - m_totalHistogramMilliseconds = m_numBuckets * m_bucketMilliseconds; - - m_histogram = new long[m_numBuckets]; - Zero(); - m_lastBucket = 0; - m_timeBase = Util.EnvironmentTickCount(); - } - - public void Event() - { - this.Event(1); - } - - // Record an event at time 'now' in the histogram. - public void Event(int cnt) - { - lock (histoLock) - { - // The time as displaced from the base of the histogram - int bucketTime = Util.EnvironmentTickCountSubtract(m_timeBase); - - // If more than the total time of the histogram, we just start over - if (bucketTime > m_totalHistogramMilliseconds) - { - Zero(); - m_lastBucket = 0; - m_timeBase = Util.EnvironmentTickCount(); - } - else - { - // To which bucket should we add this event? - int bucket = bucketTime / m_bucketMilliseconds; - - // Advance m_lastBucket to the new bucket. Zero any buckets skipped over. - while (bucket != m_lastBucket) - { - // Zero from just after the last bucket to the new bucket or the end - for (int jj = m_lastBucket + 1; jj <= Math.Min(bucket, m_numBuckets - 1); jj++) - { - m_histogram[jj] = 0; - } - m_lastBucket = bucket; - // If the new bucket is off the end, wrap around to the beginning - if (bucket > m_numBuckets) - { - bucket -= m_numBuckets; - m_lastBucket = 0; - m_histogram[m_lastBucket] = 0; - m_timeBase += m_totalHistogramMilliseconds; - } - } - } - m_histogram[m_lastBucket] += cnt; - } - } - - // Get a copy of the current histogram - public long[] GetHistogram() - { - long[] ret = new long[m_numBuckets]; - lock (histoLock) - { - int indx = m_lastBucket + 1; - for (int ii = 0; ii < m_numBuckets; ii++, indx++) - { - if (indx >= m_numBuckets) - indx = 0; - ret[ii] = m_histogram[indx]; - } - } - return ret; - } - - public OSDMap GetHistogramAsOSDMap() - { - OSDMap ret = new OSDMap(); - - ret.Add("Buckets", OSD.FromInteger(m_numBuckets)); - ret.Add("BucketMilliseconds", OSD.FromInteger(m_bucketMilliseconds)); - ret.Add("TotalMilliseconds", OSD.FromInteger(m_totalHistogramMilliseconds)); - - // Compute a number for the first bucket in the histogram. - // This will allow readers to know how this histogram relates to any previously read histogram. - int baseBucketNum = (m_timeBase / m_bucketMilliseconds) + m_lastBucket + 1; - ret.Add("BaseNumber", OSD.FromInteger(baseBucketNum)); - - ret.Add("Values", GetHistogramAsOSDArray()); - - return ret; - } - // Get a copy of the current histogram - public OSDArray GetHistogramAsOSDArray() - { - OSDArray ret = new OSDArray(m_numBuckets); - lock (histoLock) - { - int indx = m_lastBucket + 1; - for (int ii = 0; ii < m_numBuckets; ii++, indx++) - { - if (indx >= m_numBuckets) - indx = 0; - ret[ii] = OSD.FromLong(m_histogram[indx]); - } - } - return ret; - } - - // Zero out the histogram - public void Zero() - { - lock (histoLock) - { - for (int ii = 0; ii < m_numBuckets; ii++) - m_histogram[ii] = 0; - } - } -} - // A statistic that wraps a counter. // Built this way mostly so histograms and history can be created. public class CounterStat : Stat @@ -236,12 +100,18 @@ public class CounterStat : Stat // If there are any histograms, add a new field that is an array of histograms as OSDMaps if (m_histograms.Count > 0) { - OSDArray histos = new OSDArray(); - foreach (EventHistogram histo in m_histograms.Values) + lock (counterLock) { - histos.Add(histo.GetHistogramAsOSDMap()); + if (m_histograms.Count > 0) + { + OSDArray histos = new OSDArray(); + foreach (EventHistogram histo in m_histograms.Values) + { + histos.Add(histo.GetHistogramAsOSDMap()); + } + map.Add("Histograms", histos); + } } - map.Add("Histograms", histos); } return map; } diff --git a/OpenSim/Framework/Monitoring/Stats/EventHistogram.cs b/OpenSim/Framework/Monitoring/Stats/EventHistogram.cs new file mode 100755 index 0000000000..f51f32247e --- /dev/null +++ b/OpenSim/Framework/Monitoring/Stats/EventHistogram.cs @@ -0,0 +1,173 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using OpenMetaverse.StructuredData; + +namespace OpenSim.Framework.Monitoring +{ +// Create a time histogram of events. The histogram is built in a wrap-around +// array of equally distributed buckets. +// For instance, a minute long histogram of second sized buckets would be: +// new EventHistogram(60, 1000) +public class EventHistogram +{ + private int m_timeBase; + private int m_numBuckets; + private int m_bucketMilliseconds; + private int m_lastBucket; + private int m_totalHistogramMilliseconds; + private long[] m_histogram; + private object histoLock = new object(); + + public EventHistogram(int numberOfBuckets, int millisecondsPerBucket) + { + m_numBuckets = numberOfBuckets; + m_bucketMilliseconds = millisecondsPerBucket; + m_totalHistogramMilliseconds = m_numBuckets * m_bucketMilliseconds; + + m_histogram = new long[m_numBuckets]; + Zero(); + m_lastBucket = 0; + m_timeBase = Util.EnvironmentTickCount(); + } + + public void Event() + { + this.Event(1); + } + + // Record an event at time 'now' in the histogram. + public void Event(int cnt) + { + lock (histoLock) + { + // The time as displaced from the base of the histogram + int bucketTime = Util.EnvironmentTickCountSubtract(m_timeBase); + + // If more than the total time of the histogram, we just start over + if (bucketTime > m_totalHistogramMilliseconds) + { + Zero(); + m_lastBucket = 0; + m_timeBase = Util.EnvironmentTickCount(); + } + else + { + // To which bucket should we add this event? + int bucket = bucketTime / m_bucketMilliseconds; + + // Advance m_lastBucket to the new bucket. Zero any buckets skipped over. + while (bucket != m_lastBucket) + { + // Zero from just after the last bucket to the new bucket or the end + for (int jj = m_lastBucket + 1; jj <= Math.Min(bucket, m_numBuckets - 1); jj++) + { + m_histogram[jj] = 0; + } + m_lastBucket = bucket; + // If the new bucket is off the end, wrap around to the beginning + if (bucket > m_numBuckets) + { + bucket -= m_numBuckets; + m_lastBucket = 0; + m_histogram[m_lastBucket] = 0; + m_timeBase += m_totalHistogramMilliseconds; + } + } + } + m_histogram[m_lastBucket] += cnt; + } + } + + // Get a copy of the current histogram + public long[] GetHistogram() + { + long[] ret = new long[m_numBuckets]; + lock (histoLock) + { + int indx = m_lastBucket + 1; + for (int ii = 0; ii < m_numBuckets; ii++, indx++) + { + if (indx >= m_numBuckets) + indx = 0; + ret[ii] = m_histogram[indx]; + } + } + return ret; + } + + public OSDMap GetHistogramAsOSDMap() + { + OSDMap ret = new OSDMap(); + + ret.Add("Buckets", OSD.FromInteger(m_numBuckets)); + ret.Add("BucketMilliseconds", OSD.FromInteger(m_bucketMilliseconds)); + ret.Add("TotalMilliseconds", OSD.FromInteger(m_totalHistogramMilliseconds)); + + // Compute a number for the first bucket in the histogram. + // This will allow readers to know how this histogram relates to any previously read histogram. + int baseBucketNum = (m_timeBase / m_bucketMilliseconds) + m_lastBucket + 1; + ret.Add("BaseNumber", OSD.FromInteger(baseBucketNum)); + + ret.Add("Values", GetHistogramAsOSDArray()); + + return ret; + } + // Get a copy of the current histogram + public OSDArray GetHistogramAsOSDArray() + { + OSDArray ret = new OSDArray(m_numBuckets); + lock (histoLock) + { + int indx = m_lastBucket + 1; + for (int ii = 0; ii < m_numBuckets; ii++, indx++) + { + if (indx >= m_numBuckets) + indx = 0; + ret[ii] = OSD.FromLong(m_histogram[indx]); + } + } + return ret; + } + + // Zero out the histogram + public void Zero() + { + lock (histoLock) + { + for (int ii = 0; ii < m_numBuckets; ii++) + m_histogram[ii] = 0; + } + } +} + +} diff --git a/OpenSim/Framework/Monitoring/Stats/PercentageStat.cs b/OpenSim/Framework/Monitoring/Stats/PercentageStat.cs index 60bed55500..55ddf0681c 100644 --- a/OpenSim/Framework/Monitoring/Stats/PercentageStat.cs +++ b/OpenSim/Framework/Monitoring/Stats/PercentageStat.cs @@ -29,6 +29,8 @@ using System; using System.Collections.Generic; using System.Text; +using OpenMetaverse.StructuredData; + namespace OpenSim.Framework.Monitoring { public class PercentageStat : Stat @@ -84,5 +86,19 @@ namespace OpenSim.Framework.Monitoring return sb.ToString(); } + + // PercentageStat is a basic stat plus percent calc + public override OSDMap ToOSDMap() + { + // Get the foundational instance + OSDMap map = base.ToOSDMap(); + + map["StatType"] = "PercentageStat"; + + map.Add("Antecedent", OSD.FromLong(Antecedent)); + map.Add("Consequent", OSD.FromLong(Consequent)); + + return map; + } } } \ No newline at end of file diff --git a/OpenSim/Framework/Monitoring/Stats/Stat.cs b/OpenSim/Framework/Monitoring/Stats/Stat.cs index ffd5132822..2b34493c5f 100644 --- a/OpenSim/Framework/Monitoring/Stats/Stat.cs +++ b/OpenSim/Framework/Monitoring/Stats/Stat.cs @@ -241,6 +241,8 @@ namespace OpenSim.Framework.Monitoring public virtual OSDMap ToOSDMap() { OSDMap ret = new OSDMap(); + ret.Add("StatType", "Stat"); // used by overloading classes to denote type of stat + ret.Add("Category", OSD.FromString(Category)); ret.Add("Container", OSD.FromString(Container)); ret.Add("ShortName", OSD.FromString(ShortName)); @@ -248,26 +250,36 @@ namespace OpenSim.Framework.Monitoring ret.Add("Description", OSD.FromString(Description)); ret.Add("UnitName", OSD.FromString(UnitName)); ret.Add("Value", OSD.FromReal(Value)); - ret.Add("StatType", "Stat"); // used by overloading classes to denote type of stat + + double lastChangeOverTime, averageChangeOverTime; + if (ComputeMeasuresOfInterest(out lastChangeOverTime, out averageChangeOverTime)) + { + ret.Add("LastChangeOverTime", OSD.FromReal(lastChangeOverTime)); + ret.Add("AverageChangeOverTime", OSD.FromReal(averageChangeOverTime)); + } return ret; } - protected void AppendMeasuresOfInterest(StringBuilder sb) + // Compute the averages over time and return same. + // Return 'true' if averages were actually computed. 'false' if no average info. + public bool ComputeMeasuresOfInterest(out double lastChangeOverTime, out double averageChangeOverTime) { - if ((MeasuresOfInterest & MeasuresOfInterest.AverageChangeOverTime) - == MeasuresOfInterest.AverageChangeOverTime) + bool ret = false; + lastChangeOverTime = 0; + averageChangeOverTime = 0; + + if ((MeasuresOfInterest & MeasuresOfInterest.AverageChangeOverTime) == MeasuresOfInterest.AverageChangeOverTime) { double totalChange = 0; - double lastChangeOverTime = 0; double? penultimateSample = null; double? lastSample = null; lock (m_samples) { -// m_log.DebugFormat( -// "[STAT]: Samples for {0} are {1}", -// Name, string.Join(",", m_samples.Select(s => s.ToString()).ToArray())); + // m_log.DebugFormat( + // "[STAT]: Samples for {0} are {1}", + // Name, string.Join(",", m_samples.Select(s => s.ToString()).ToArray())); foreach (double s in m_samples) { @@ -280,13 +292,27 @@ namespace OpenSim.Framework.Monitoring } if (lastSample != null && penultimateSample != null) - lastChangeOverTime + { + lastChangeOverTime = ((double)lastSample - (double)penultimateSample) / (Watchdog.WATCHDOG_INTERVAL_MS / 1000); + } int divisor = m_samples.Count <= 1 ? 1 : m_samples.Count - 1; - double averageChangeOverTime = totalChange / divisor / (Watchdog.WATCHDOG_INTERVAL_MS / 1000); + averageChangeOverTime = totalChange / divisor / (Watchdog.WATCHDOG_INTERVAL_MS / 1000); + ret = true; + } + return ret; + } + + protected void AppendMeasuresOfInterest(StringBuilder sb) + { + double lastChangeOverTime = 0; + double averageChangeOverTime = 0; + + if (ComputeMeasuresOfInterest(out lastChangeOverTime, out averageChangeOverTime)) + { sb.AppendFormat( ", {0:0.##}{1}/s, {2:0.##}{3}/s", lastChangeOverTime,