From 5eb6b1485452a7c1766b7df2e9ea39536a893260 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Thu, 28 Aug 2014 18:15:33 +0100 Subject: [PATCH] On code section that rezzes single objects and attachments, reduce CPU use by reading asset XML a single time with a stream reader rather than multiple times. Reading large XML documents (e.g. complex attachments) is CPU expensive - this must be done as few times as possible (preferably just once). Reading these documents into XmlDocument is also more resource intensive than using XmlTextReader, as per Microsoft's own publication "Improve .NET Application Performance and Scalability" Optimization of other cases will follow if this change is successful. --- .../InventoryAccess/InventoryAccessModule.cs | 6 +- .../Tests/InventoryAccessModuleTests.cs | 1 + .../Framework/Scenes/Scene.Inventory.cs | 95 ++++++++++++------- .../Framework/Scenes/SceneObjectGroup.cs | 28 ++++++ .../Serialization/SceneObjectSerializer.cs | 67 ++++++------- 5 files changed, 127 insertions(+), 70 deletions(-) diff --git a/OpenSim/Region/CoreModules/Framework/InventoryAccess/InventoryAccessModule.cs b/OpenSim/Region/CoreModules/Framework/InventoryAccess/InventoryAccessModule.cs index b4771fd2ca..8528c65f32 100644 --- a/OpenSim/Region/CoreModules/Framework/InventoryAccess/InventoryAccessModule.cs +++ b/OpenSim/Region/CoreModules/Framework/InventoryAccess/InventoryAccessModule.cs @@ -798,7 +798,7 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess UUID RayTargetID, byte BypassRayCast, bool RayEndIsIntersection, bool RezSelected, bool RemoveItem, UUID fromTaskID, bool attachment) { - AssetBase rezAsset = m_Scene.AssetService.Get(assetID.ToString()); + AssetBase rezAsset = m_Scene.AssetService.Get(assetID.ToString()); if (rezAsset == null) { @@ -829,7 +829,9 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess byte bRayEndIsIntersection = (byte)(RayEndIsIntersection ? 1 : 0); Vector3 pos; - bool single = m_Scene.GetObjectsToRez(rezAsset.Data, attachment, out objlist, out veclist, out bbox, out offsetHeight); + bool single + = m_Scene.GetObjectsToRez( + rezAsset.Data, attachment, out objlist, out veclist, out bbox, out offsetHeight); if (single) { diff --git a/OpenSim/Region/CoreModules/Framework/InventoryAccess/Tests/InventoryAccessModuleTests.cs b/OpenSim/Region/CoreModules/Framework/InventoryAccess/Tests/InventoryAccessModuleTests.cs index ad1a0e1a96..80b9c0af78 100644 --- a/OpenSim/Region/CoreModules/Framework/InventoryAccess/Tests/InventoryAccessModuleTests.cs +++ b/OpenSim/Region/CoreModules/Framework/InventoryAccess/Tests/InventoryAccessModuleTests.cs @@ -111,6 +111,7 @@ namespace OpenSim.Region.CoreModules.Framework.InventoryAccess.Tests InventoryFolderBase objsFolder = InventoryArchiveUtils.FindFoldersByPath(m_scene.InventoryService, m_userId, "Objects")[0]; item1.Folder = objsFolder.ID; + item1.Flags |= (uint)InventoryItemFlags.ObjectHasMultipleItems; m_scene.AddInventoryItem(item1); SceneObjectGroup so diff --git a/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs b/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs index 542d454629..cbb4fe771a 100644 --- a/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.Inventory.cs @@ -30,6 +30,7 @@ using System.Collections.Generic; using System.Collections; using System.Reflection; using System.Text; +using System.Threading; using System.Timers; using System.Xml; using OpenMetaverse; @@ -2192,60 +2193,84 @@ namespace OpenSim.Region.Framework.Scenes /// Returns one object if the asset is a regular object, and multiple objects for a coalesced object. /// /// Asset data - /// Whether the item is an attachment + /// True if the object is an attachment. /// The objects included in the asset /// Relative positions of the objects /// Bounding box of all the objects /// Offset in the Z axis from the centre of the bounding box /// to the centre of the root prim (relevant only when returning a single object) - /// true = returning a single object; false = multiple objects - public bool GetObjectsToRez(byte[] assetData, bool attachment, out List objlist, out List veclist, + /// + /// true if returning a single object or deserialization fails, false if returning the coalesced + /// list of objects + /// + public bool GetObjectsToRez( + byte[] assetData, bool isAttachment, out List objlist, out List veclist, out Vector3 bbox, out float offsetHeight) { objlist = new List(); veclist = new List(); - XmlDocument doc = new XmlDocument(); string xmlData = Utils.BytesToString(assetData); - doc.LoadXml(xmlData); - XmlElement e = (XmlElement)doc.SelectSingleNode("/CoalescedObject"); - if (e == null || attachment) // Single + try { - SceneObjectGroup g = SceneObjectSerializer.FromOriginalXmlFormat(xmlData); - objlist.Add(g); - veclist.Add(new Vector3(0, 0, 0)); - bbox = g.GetAxisAlignedBoundingBox(out offsetHeight); - return true; - } - else - { - XmlElement coll = (XmlElement)e; - float bx = Convert.ToSingle(coll.GetAttribute("x")); - float by = Convert.ToSingle(coll.GetAttribute("y")); - float bz = Convert.ToSingle(coll.GetAttribute("z")); - bbox = new Vector3(bx, by, bz); - offsetHeight = 0; - - XmlNodeList groups = e.SelectNodes("SceneObjectGroup"); - foreach (XmlNode n in groups) + using (XmlTextReader reader = new XmlTextReader(xmlData, XmlNodeType.Element, null)) { - SceneObjectGroup g = SceneObjectSerializer.FromOriginalXmlFormat(n.OuterXml); - objlist.Add(g); + reader.Read(); + bool isSingleObject = reader.Name != "CoalescedObject"; - XmlElement el = (XmlElement)n; - string rawX = el.GetAttribute("offsetx"); - string rawY = el.GetAttribute("offsety"); - string rawZ = el.GetAttribute("offsetz"); + if (isSingleObject || isAttachment) + { + SceneObjectGroup g = SceneObjectSerializer.FromOriginalXmlFormat(reader); + objlist.Add(g); + veclist.Add(Vector3.Zero); + bbox = g.GetAxisAlignedBoundingBox(out offsetHeight); + return true; + } + else + { + XmlDocument doc = new XmlDocument(); + doc.LoadXml(xmlData); + XmlElement e = (XmlElement)doc.SelectSingleNode("/CoalescedObject"); + XmlElement coll = (XmlElement)e; + float bx = Convert.ToSingle(coll.GetAttribute("x")); + float by = Convert.ToSingle(coll.GetAttribute("y")); + float bz = Convert.ToSingle(coll.GetAttribute("z")); + bbox = new Vector3(bx, by, bz); + offsetHeight = 0; - float x = Convert.ToSingle(rawX); - float y = Convert.ToSingle(rawY); - float z = Convert.ToSingle(rawZ); - veclist.Add(new Vector3(x, y, z)); + XmlNodeList groups = e.SelectNodes("SceneObjectGroup"); + foreach (XmlNode n in groups) + { + SceneObjectGroup g = SceneObjectSerializer.FromOriginalXmlFormat(n.OuterXml); + objlist.Add(g); + + XmlElement el = (XmlElement)n; + string rawX = el.GetAttribute("offsetx"); + string rawY = el.GetAttribute("offsety"); + string rawZ = el.GetAttribute("offsetz"); + + float x = Convert.ToSingle(rawX); + float y = Convert.ToSingle(rawY); + float z = Convert.ToSingle(rawZ); + veclist.Add(new Vector3(x, y, z)); + } + + return false; + } } } + catch (Exception e) + { + m_log.Error( + "[AGENT INVENTORY]: Deserialization of xml failed when looking for CoalescedObject tag. Exception ", + e); - return false; + bbox = Vector3.Zero; + offsetHeight = 0; + } + + return true; } /// diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs index 69491b7e1d..c95d2076a4 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs @@ -902,6 +902,34 @@ namespace OpenSim.Region.Framework.Scenes } } + public void LoadScriptState(XmlTextReader reader) + { +// m_log.DebugFormat("[SCENE OBJECT GROUP]: Looking for script state for {0} in {1}", Name); + + while (reader.ReadToFollowing("SavedScriptState")) + { +// m_log.DebugFormat("[SCENE OBJECT GROUP]: Loading script state for {0}", Name); + + if (m_savedScriptState == null) + m_savedScriptState = new Dictionary(); + + string uuid = reader.GetAttribute("UUID"); + + if (uuid != null) + { +// m_log.DebugFormat("[SCENE OBJECT GROUP]: Found state for item ID {0} in object {1}", uuid, Name); + + UUID itemid = new UUID(uuid); + if (itemid != UUID.Zero) + m_savedScriptState[itemid] = reader.ReadInnerXml(); + } + else + { + m_log.WarnFormat("[SCENE OBJECT GROUP]: SavedScriptState element had no UUID in object {0}", Name); + } + } + } + /// /// Hooks this object up to the backup event so that it is persisted to the database when the update thread executes. /// diff --git a/OpenSim/Region/Framework/Scenes/Serialization/SceneObjectSerializer.cs b/OpenSim/Region/Framework/Scenes/Serialization/SceneObjectSerializer.cs index e68f954956..8f99dc6f8e 100644 --- a/OpenSim/Region/Framework/Scenes/Serialization/SceneObjectSerializer.cs +++ b/OpenSim/Region/Framework/Scenes/Serialization/SceneObjectSerializer.cs @@ -58,58 +58,59 @@ namespace OpenSim.Region.Framework.Scenes.Serialization /// /// The scene object deserialized. Null on failure. public static SceneObjectGroup FromOriginalXmlFormat(string xmlData) + { + using (XmlTextReader reader = new XmlTextReader(xmlData, XmlNodeType.Element, null)) + return FromOriginalXmlFormat(reader); + } + + /// + /// Deserialize a scene object from the original xml format + /// + /// + /// The scene object deserialized. Null on failure. + public static SceneObjectGroup FromOriginalXmlFormat(XmlTextReader reader) { //m_log.DebugFormat("[SOG]: Starting deserialization of SOG"); //int time = System.Environment.TickCount; + SceneObjectGroup sceneObject = null; + try { - StringReader sr; - XmlTextReader reader; - XmlNodeList parts; - XmlDocument doc; int linkNum; - doc = new XmlDocument(); - doc.LoadXml(xmlData); - parts = doc.GetElementsByTagName("RootPart"); + reader.ReadToFollowing("RootPart"); + reader.ReadToFollowing("SceneObjectPart"); + sceneObject = new SceneObjectGroup(SceneObjectPart.FromXml(reader)); + reader.ReadToFollowing("OtherParts"); - if (parts.Count == 0) - throw new Exception("Invalid Xml format - no root part"); - - sr = new StringReader(parts[0].InnerXml); - reader = new XmlTextReader(sr); - SceneObjectGroup sceneObject = new SceneObjectGroup(SceneObjectPart.FromXml(reader)); - reader.Close(); - sr.Close(); - - parts = doc.GetElementsByTagName("Part"); - - for (int i = 0; i < parts.Count; i++) + if (reader.ReadToDescendant("Part")) { - sr = new StringReader(parts[i].InnerXml); - reader = new XmlTextReader(sr); - SceneObjectPart part = SceneObjectPart.FromXml(reader); - linkNum = part.LinkNum; - sceneObject.AddPart(part); - part.LinkNum = linkNum; - part.TrimPermissions(); - reader.Close(); - sr.Close(); + do + { + if (reader.ReadToDescendant("SceneObjectPart")) + { + SceneObjectPart part = SceneObjectPart.FromXml(reader); + linkNum = part.LinkNum; + sceneObject.AddPart(part); + part.LinkNum = linkNum; + part.TrimPermissions(); + } + } + while (reader.ReadToNextSibling("Part")); } // Script state may, or may not, exist. Not having any, is NOT // ever a problem. - sceneObject.LoadScriptState(doc); - - return sceneObject; + sceneObject.LoadScriptState(reader); } catch (Exception e) { - m_log.ErrorFormat( - "[SERIALIZER]: Deserialization of xml failed with {0}. xml was {1}", e, xmlData); + m_log.ErrorFormat("[SERIALIZER]: Deserialization of xml failed. Exception {0}", e); return null; } + + return sceneObject; } ///