diff --git a/OpenSim/Region/Application/OpenSim.cs b/OpenSim/Region/Application/OpenSim.cs index 6796f2bd33..c0913c5296 100644 --- a/OpenSim/Region/Application/OpenSim.cs +++ b/OpenSim/Region/Application/OpenSim.cs @@ -28,6 +28,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Reflection; using System.Text; @@ -138,7 +139,7 @@ namespace OpenSim m_log.Info("===================================================================="); m_log.Info("========================= STARTING OPENSIM ========================="); m_log.Info("===================================================================="); - + //m_log.InfoFormat("[OPENSIM MAIN]: GC Is Server GC: {0}", GCSettings.IsServerGC.ToString()); // http://msdn.microsoft.com/en-us/library/bb384202.aspx //GCSettings.LatencyMode = GCLatencyMode.Batch; diff --git a/OpenSim/Region/CoreModules/Avatar/Commands/UserCommandsModule.cs b/OpenSim/Region/CoreModules/Avatar/Commands/UserCommandsModule.cs new file mode 100644 index 0000000000..4bcd2ac499 --- /dev/null +++ b/OpenSim/Region/CoreModules/Avatar/Commands/UserCommandsModule.cs @@ -0,0 +1,189 @@ +/* + * 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.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using log4net; +using Mono.Addins; +using NDesk.Options; +using Nini.Config; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Console; +using OpenSim.Framework.Statistics; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.CoreModules.Avatars.Commands +{ + /// + /// A module that holds commands for manipulating objects in the scene. + /// + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "UserCommandsModule")] + public class UserCommandsModule : ISharedRegionModule + { +// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + public const string TeleportUserCommandSyntax = "teleport user "; + + public static Regex InterRegionDestinationRegex + = new Regex(@"^(?.+)/(?\d+)/(?\d+)/(?\d+)$", RegexOptions.Compiled); + + public static Regex WithinRegionDestinationRegex + = new Regex(@"^(?\d+)/(?\d+)/(?\d+)$", RegexOptions.Compiled); + + private Dictionary m_scenes = new Dictionary(); + + public string Name { get { return "User Commands Module"; } } + + public Type ReplaceableInterface { get { return null; } } + + public void Initialise(IConfigSource source) + { +// m_log.DebugFormat("[USER COMMANDS MODULE]: INITIALIZED MODULE"); + } + + public void PostInitialise() + { +// m_log.DebugFormat("[USER COMMANDS MODULE]: POST INITIALIZED MODULE"); + } + + public void Close() + { +// m_log.DebugFormat("[USER COMMANDS MODULE]: CLOSED MODULE"); + } + + public void AddRegion(Scene scene) + { +// m_log.DebugFormat("[USER COMMANDS MODULE]: REGION {0} ADDED", scene.RegionInfo.RegionName); + + lock (m_scenes) + m_scenes[scene.RegionInfo.RegionID] = scene; + + scene.AddCommand( + "Users", + this, + "teleport user", + TeleportUserCommandSyntax, + "Teleport a user in this simulator to the given destination", + " is in format []///, e.g. regionone/20/30/40 or just 20/30/40 to teleport within same region." + + "\nIf the region contains a space then the whole destination must be in quotes, e.g. \"region one/20/30/40\"", + HandleTeleportUser); + } + + public void RemoveRegion(Scene scene) + { +// m_log.DebugFormat("[USER COMMANDS MODULE]: REGION {0} REMOVED", scene.RegionInfo.RegionName); + + lock (m_scenes) + m_scenes.Remove(scene.RegionInfo.RegionID); + } + + public void RegionLoaded(Scene scene) + { +// m_log.DebugFormat("[USER COMMANDS MODULE]: REGION {0} LOADED", scene.RegionInfo.RegionName); + } + + private ScenePresence GetUser(string firstName, string lastName) + { + ScenePresence userFound = null; + + lock (m_scenes) + { + foreach (Scene scene in m_scenes.Values) + { + ScenePresence user = scene.GetScenePresence(firstName, lastName); + if (user != null && !user.IsChildAgent) + { + userFound = user; + break; + } + } + } + + return userFound; + } + + private void HandleTeleportUser(string module, string[] cmd) + { + if (cmd.Length < 5) + { + MainConsole.Instance.OutputFormat("Usage: " + TeleportUserCommandSyntax); + return; + } + + string firstName = cmd[2]; + string lastName = cmd[3]; + string rawDestination = cmd[4]; + + ScenePresence user = GetUser(firstName, lastName); + + if (user == null) + { + MainConsole.Instance.OutputFormat("No user found with name {0} {1}", firstName, lastName); + return; + } + +// MainConsole.Instance.OutputFormat("rawDestination [{0}]", rawDestination); + + Match m = WithinRegionDestinationRegex.Match(rawDestination); + + if (!m.Success) + { + m = InterRegionDestinationRegex.Match(rawDestination); + + if (!m.Success) + { + MainConsole.Instance.OutputFormat("Invalid destination {0}", rawDestination); + return; + } + } + + string regionName + = m.Groups["regionName"].Success ? m.Groups["regionName"].Value : user.Scene.RegionInfo.RegionName; + + MainConsole.Instance.OutputFormat( + "Teleporting {0} to {1},{2},{3} in {4}", + user.Name, + m.Groups["x"], m.Groups["y"], m.Groups["z"], + regionName); + + user.Scene.RequestTeleportLocation( + user.ControllingClient, + regionName, + new Vector3( + float.Parse(m.Groups["x"].Value), + float.Parse(m.Groups["y"].Value), + float.Parse(m.Groups["z"].Value)), + user.Lookat, + (uint)TeleportFlags.ViaLocation); + } + } +} \ No newline at end of file diff --git a/OpenSim/Region/CoreModules/Framework/Monitoring/MonitorModule.cs b/OpenSim/Region/CoreModules/Framework/Monitoring/MonitorModule.cs index 7f8271d401..4a8c369335 100644 --- a/OpenSim/Region/CoreModules/Framework/Monitoring/MonitorModule.cs +++ b/OpenSim/Region/CoreModules/Framework/Monitoring/MonitorModule.cs @@ -49,7 +49,16 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring public bool Enabled { get; private set; } private Scene m_scene; - private readonly List m_monitors = new List(); + + /// + /// These are monitors where we know the static details in advance. + /// + /// + /// Dynamic monitors also exist (we don't know any of the details of what stats we get back here) + /// but these are currently hardcoded. + /// + private readonly List m_staticMonitors = new List(); + private readonly List m_alerts = new List(); private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); @@ -84,9 +93,18 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring public void DebugMonitors(string module, string[] args) { - foreach (IMonitor monitor in m_monitors) + foreach (IMonitor monitor in m_staticMonitors) { - m_log.Info("[MonitorModule]: " + m_scene.RegionInfo.RegionName + " reports " + monitor.GetFriendlyName() + " = " + monitor.GetFriendlyValue()); + m_log.InfoFormat( + "[MONITOR MODULE]: {0} reports {1} = {2}", + m_scene.RegionInfo.RegionName, monitor.GetFriendlyName(), monitor.GetFriendlyValue()); + } + + foreach (KeyValuePair tuple in m_scene.StatsReporter.GetExtraSimStats()) + { + m_log.InfoFormat( + "[MONITOR MODULE]: {0} reports {1} = {2}", + m_scene.RegionInfo.RegionName, tuple.Key, tuple.Value); } } @@ -106,11 +124,12 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring { string monID = (string) request["monitor"]; - foreach (IMonitor monitor in m_monitors) + foreach (IMonitor monitor in m_staticMonitors) { string elemName = monitor.ToString(); if (elemName.StartsWith(monitor.GetType().Namespace)) elemName = elemName.Substring(monitor.GetType().Namespace.Length + 1); + if (elemName == monID || monitor.ToString() == monID) { Hashtable ereply3 = new Hashtable(); @@ -123,6 +142,9 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring } } + // FIXME: Arguably this should also be done with dynamic monitors but I'm not sure what the above code + // is even doing. Why are we inspecting the type of the monitor??? + // No monitor with that name Hashtable ereply2 = new Hashtable(); @@ -134,12 +156,18 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring } string xml = ""; - foreach (IMonitor monitor in m_monitors) + foreach (IMonitor monitor in m_staticMonitors) { string elemName = monitor.GetName(); xml += "<" + elemName + ">" + monitor.GetValue().ToString() + ""; // m_log.DebugFormat("[MONITOR MODULE]: {0} = {1}", elemName, monitor.GetValue()); } + + foreach (KeyValuePair tuple in m_scene.StatsReporter.GetExtraSimStats()) + { + xml += "<" + tuple.Key + ">" + tuple.Value + ""; + } + xml += ""; Hashtable ereply = new Hashtable(); @@ -156,20 +184,20 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring if (!Enabled) return; - m_monitors.Add(new AgentCountMonitor(m_scene)); - m_monitors.Add(new ChildAgentCountMonitor(m_scene)); - m_monitors.Add(new GCMemoryMonitor()); - m_monitors.Add(new ObjectCountMonitor(m_scene)); - m_monitors.Add(new PhysicsFrameMonitor(m_scene)); - m_monitors.Add(new PhysicsUpdateFrameMonitor(m_scene)); - m_monitors.Add(new PWSMemoryMonitor()); - m_monitors.Add(new ThreadCountMonitor()); - m_monitors.Add(new TotalFrameMonitor(m_scene)); - m_monitors.Add(new EventFrameMonitor(m_scene)); - m_monitors.Add(new LandFrameMonitor(m_scene)); - m_monitors.Add(new LastFrameTimeMonitor(m_scene)); + m_staticMonitors.Add(new AgentCountMonitor(m_scene)); + m_staticMonitors.Add(new ChildAgentCountMonitor(m_scene)); + m_staticMonitors.Add(new GCMemoryMonitor()); + m_staticMonitors.Add(new ObjectCountMonitor(m_scene)); + m_staticMonitors.Add(new PhysicsFrameMonitor(m_scene)); + m_staticMonitors.Add(new PhysicsUpdateFrameMonitor(m_scene)); + m_staticMonitors.Add(new PWSMemoryMonitor()); + m_staticMonitors.Add(new ThreadCountMonitor()); + m_staticMonitors.Add(new TotalFrameMonitor(m_scene)); + m_staticMonitors.Add(new EventFrameMonitor(m_scene)); + m_staticMonitors.Add(new LandFrameMonitor(m_scene)); + m_staticMonitors.Add(new LastFrameTimeMonitor(m_scene)); - m_monitors.Add( + m_staticMonitors.Add( new GenericMonitor( m_scene, "TimeDilationMonitor", @@ -177,7 +205,7 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring m => m.Scene.StatsReporter.LastReportedSimStats[0], m => m.GetValue().ToString())); - m_monitors.Add( + m_staticMonitors.Add( new GenericMonitor( m_scene, "SimFPSMonitor", @@ -185,7 +213,7 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring m => m.Scene.StatsReporter.LastReportedSimStats[1], m => string.Format("{0}", m.GetValue()))); - m_monitors.Add( + m_staticMonitors.Add( new GenericMonitor( m_scene, "PhysicsFPSMonitor", @@ -193,7 +221,7 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring m => m.Scene.StatsReporter.LastReportedSimStats[2], m => string.Format("{0}", m.GetValue()))); - m_monitors.Add( + m_staticMonitors.Add( new GenericMonitor( m_scene, "AgentUpdatesPerSecondMonitor", @@ -201,15 +229,7 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring m => m.Scene.StatsReporter.LastReportedSimStats[3], m => string.Format("{0} per second", m.GetValue()))); - m_monitors.Add( - new GenericMonitor( - m_scene, - "ObjectUpdatesPerSecondMonitor", - "Object Updates", - m => m.Scene.StatsReporter.LastReportedObjectUpdates, - m => string.Format("{0} per second", m.GetValue()))); - - m_monitors.Add( + m_staticMonitors.Add( new GenericMonitor( m_scene, "ActiveObjectCountMonitor", @@ -217,7 +237,7 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring m => m.Scene.StatsReporter.LastReportedSimStats[7], m => string.Format("{0}", m.GetValue()))); - m_monitors.Add( + m_staticMonitors.Add( new GenericMonitor( m_scene, "ActiveScriptsMonitor", @@ -225,7 +245,7 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring m => m.Scene.StatsReporter.LastReportedSimStats[19], m => string.Format("{0}", m.GetValue()))); - m_monitors.Add( + m_staticMonitors.Add( new GenericMonitor( m_scene, "ScriptEventsPerSecondMonitor", @@ -233,7 +253,7 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring m => m.Scene.StatsReporter.LastReportedSimStats[20], m => string.Format("{0} per second", m.GetValue()))); - m_monitors.Add( + m_staticMonitors.Add( new GenericMonitor( m_scene, "InPacketsPerSecondMonitor", @@ -241,7 +261,7 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring m => m.Scene.StatsReporter.LastReportedSimStats[13], m => string.Format("{0} per second", m.GetValue()))); - m_monitors.Add( + m_staticMonitors.Add( new GenericMonitor( m_scene, "OutPacketsPerSecondMonitor", @@ -249,7 +269,7 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring m => m.Scene.StatsReporter.LastReportedSimStats[14], m => string.Format("{0} per second", m.GetValue()))); - m_monitors.Add( + m_staticMonitors.Add( new GenericMonitor( m_scene, "UnackedBytesMonitor", @@ -257,7 +277,7 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring m => m.Scene.StatsReporter.LastReportedSimStats[15], m => string.Format("{0}", m.GetValue()))); - m_monitors.Add( + m_staticMonitors.Add( new GenericMonitor( m_scene, "PendingDownloadsMonitor", @@ -265,7 +285,7 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring m => m.Scene.StatsReporter.LastReportedSimStats[17], m => string.Format("{0}", m.GetValue()))); - m_monitors.Add( + m_staticMonitors.Add( new GenericMonitor( m_scene, "PendingUploadsMonitor", @@ -273,7 +293,7 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring m => m.Scene.StatsReporter.LastReportedSimStats[18], m => string.Format("{0}", m.GetValue()))); - m_monitors.Add( + m_staticMonitors.Add( new GenericMonitor( m_scene, "TotalFrameTimeMonitor", @@ -281,7 +301,7 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring m => m.Scene.StatsReporter.LastReportedSimStats[8], m => string.Format("{0} ms", m.GetValue()))); - m_monitors.Add( + m_staticMonitors.Add( new GenericMonitor( m_scene, "NetFrameTimeMonitor", @@ -289,7 +309,7 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring m => m.Scene.StatsReporter.LastReportedSimStats[9], m => string.Format("{0} ms", m.GetValue()))); - m_monitors.Add( + m_staticMonitors.Add( new GenericMonitor( m_scene, "PhysicsFrameTimeMonitor", @@ -297,7 +317,7 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring m => m.Scene.StatsReporter.LastReportedSimStats[10], m => string.Format("{0} ms", m.GetValue()))); - m_monitors.Add( + m_staticMonitors.Add( new GenericMonitor( m_scene, "SimulationFrameTimeMonitor", @@ -305,7 +325,7 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring m => m.Scene.StatsReporter.LastReportedSimStats[12], m => string.Format("{0} ms", m.GetValue()))); - m_monitors.Add( + m_staticMonitors.Add( new GenericMonitor( m_scene, "AgentFrameTimeMonitor", @@ -313,7 +333,7 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring m => m.Scene.StatsReporter.LastReportedSimStats[16], m => string.Format("{0} ms", m.GetValue()))); - m_monitors.Add( + m_staticMonitors.Add( new GenericMonitor( m_scene, "ImagesFrameTimeMonitor", @@ -321,7 +341,7 @@ namespace OpenSim.Region.CoreModules.Framework.Monitoring m => m.Scene.StatsReporter.LastReportedSimStats[11], m => string.Format("{0} ms", m.GetValue()))); - m_alerts.Add(new DeadlockAlert(m_monitors.Find(x => x is LastFrameTimeMonitor) as LastFrameTimeMonitor)); + m_alerts.Add(new DeadlockAlert(m_staticMonitors.Find(x => x is LastFrameTimeMonitor) as LastFrameTimeMonitor)); foreach (IAlert alert in m_alerts) { diff --git a/OpenSim/Region/Framework/Scenes/SimStatsReporter.cs b/OpenSim/Region/Framework/Scenes/SimStatsReporter.cs index 5c56264ec4..08d8d7c28f 100644 --- a/OpenSim/Region/Framework/Scenes/SimStatsReporter.cs +++ b/OpenSim/Region/Framework/Scenes/SimStatsReporter.cs @@ -26,7 +26,7 @@ */ using System; -//using System.Collections.Generic; +using System.Collections.Generic; using System.Timers; using OpenMetaverse.Packets; using OpenSim.Framework; @@ -35,10 +35,18 @@ using OpenSim.Region.Framework.Interfaces; namespace OpenSim.Region.Framework.Scenes { + /// + /// Collect statistics from the scene to send to the client and for access by other monitoring tools. + /// + /// + /// FIXME: This should be a monitoring region module + /// public class SimStatsReporter { -// private static readonly log4net.ILog m_log -// = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + private static readonly log4net.ILog m_log + = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); + + public const string LastReportedObjectUpdateStatName = "LastReportedObjectUpdates"; public delegate void SendStatResult(SimStats stats); @@ -100,6 +108,14 @@ namespace OpenSim.Region.Framework.Scenes get { return lastReportedSimStats; } } + /// + /// Extra sim statistics that are used by monitors but not sent to the client. + /// + /// + /// The keys are the stat names. + /// + private Dictionary m_lastReportedExtraSimStats = new Dictionary(); + // Sending a stats update every 3 seconds- private int statsUpdatesEveryMS = 3000; private float statsUpdateFactor = 0; @@ -334,7 +350,20 @@ namespace OpenSim.Region.Framework.Scenes } // Extra statistics that aren't currently sent to clients - LastReportedObjectUpdates = m_objectUpdates / statsUpdateFactor; + lock (m_lastReportedExtraSimStats) + { + m_lastReportedExtraSimStats[LastReportedObjectUpdateStatName] = m_objectUpdates / statsUpdateFactor; + + Dictionary physicsStats = m_scene.PhysicsScene.GetStats(); + + if (physicsStats != null) + { + foreach (KeyValuePair tuple in physicsStats) + { + m_lastReportedExtraSimStats[tuple.Key] = tuple.Value / statsUpdateFactor; + } + } + } resetvalues(); } @@ -487,7 +516,10 @@ namespace OpenSim.Region.Framework.Scenes public void AddPendingDownloads(int count) { m_pendingDownloads += count; - if (m_pendingDownloads < 0) m_pendingDownloads = 0; + + if (m_pendingDownloads < 0) + m_pendingDownloads = 0; + //m_log.InfoFormat("[stats]: Adding {0} to pending downloads to make {1}", count, m_pendingDownloads); } @@ -509,5 +541,11 @@ namespace OpenSim.Region.Framework.Scenes } #endregion + + public Dictionary GetExtraSimStats() + { + lock (m_lastReportedExtraSimStats) + return new Dictionary(m_lastReportedExtraSimStats); + } } } diff --git a/OpenSim/Region/Physics/Manager/PhysicsScene.cs b/OpenSim/Region/Physics/Manager/PhysicsScene.cs index 2a6163c0e5..b32cd30bec 100644 --- a/OpenSim/Region/Physics/Manager/PhysicsScene.cs +++ b/OpenSim/Region/Physics/Manager/PhysicsScene.cs @@ -192,8 +192,22 @@ namespace OpenSim.Region.Physics.Manager public abstract void AddPhysicsActorTaint(PhysicsActor prim); + /// + /// Perform a simulation of the current physics scene over the given timestep. + /// + /// + /// The number of frames simulated over that period. public abstract float Simulate(float timeStep); + /// + /// Get statistics about this scene. + /// + /// This facility is currently experimental and subject to change. + /// + /// A dictionary where the key is the statistic name. If no statistics are supplied then returns null. + /// + public virtual Dictionary GetStats() { return null; } + public abstract void GetResults(); public abstract void SetTerrain(float[] heightMap); diff --git a/OpenSim/Region/Physics/OdePlugin/OdeScene.cs b/OpenSim/Region/Physics/OdePlugin/OdeScene.cs index 409b27be02..04c4722b37 100644 --- a/OpenSim/Region/Physics/OdePlugin/OdeScene.cs +++ b/OpenSim/Region/Physics/OdePlugin/OdeScene.cs @@ -30,20 +30,21 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Threading; -using System.IO; -using System.Diagnostics; using log4net; using Nini.Config; using Ode.NET; +using OpenMetaverse; #if USE_DRAWSTUFF using Drawstuff.NET; #endif using OpenSim.Framework; using OpenSim.Region.Physics.Manager; -using OpenMetaverse; namespace OpenSim.Region.Physics.OdePlugin { @@ -54,15 +55,15 @@ namespace OpenSim.Region.Physics.OdePlugin End = 2 } - public struct sCollisionData - { - public uint ColliderLocalId; - public uint CollidedWithLocalId; - public int NumberOfCollisions; - public int CollisionType; - public int StatusIndicator; - public int lastframe; - } +// public struct sCollisionData +// { +// public uint ColliderLocalId; +// public uint CollidedWithLocalId; +// public int NumberOfCollisions; +// public int CollisionType; +// public int StatusIndicator; +// public int lastframe; +// } [Flags] public enum CollisionCategories : int @@ -131,6 +132,80 @@ namespace OpenSim.Region.Physics.OdePlugin /// internal static Object UniversalColliderSyncObject = new Object(); + /// + /// Is stats collecting enabled for this ODE scene? + /// + public bool CollectStats { get; set; } + + /// + /// Statistics for this scene. + /// + private Dictionary m_stats = new Dictionary(); + + /// + /// Stat name for the total time spent in ODE frame processing. + /// + /// + /// A sanity check for the main scene loop physics time. + /// + public const string ODETotalFrameMsStatName = "ODETotalFrameMS"; + + /// + /// The amount of time spent in native code that actually steps through the simulation. + /// + public const string ODENativeStepFrameMsStatName = "ODENativeStepFrameMS"; + + /// + /// Stat name for recording the number of milliseconds that ODE spends in native collision code. + /// + public const string ODENativeCollisionFrameMsStatName = "ODENativeCollisionFrameMS"; + + /// + /// Stat name for recording the number of milliseconds that ODE spends in native space collision code. + /// + public const string ODENativeSpaceCollisionFrameMsStatName = "ODENativeSpaceCollisionFrameMS"; + + /// + /// Stat name for recording the number of milliseconds that ODE spends in native geom collision code. + /// + public const string ODENativeGeomCollisionFrameMsStatName = "ODENativeGeomCollisionFrameMS"; + + /// + /// Stat name for the number of avatar collisions with another entity. + /// + public const string ODEAvatarContactsStatsName = "ODEAvatarContacts"; + + /// + /// Stat name for the number of prim collisions with another entity. + /// + public const string ODEPrimContactsStatName = "ODEPrimContacts"; + + /// + /// Used to hold tick numbers for stat collection purposes. + /// + private int m_nativeCollisionStartTick; + + /// + /// A messy way to tell if we need to avoid adding a collision time because this was already done in the callback. + /// + private bool m_inCollisionTiming; + + /// + /// A temporary holder for the number of avatar collisions in a frame, so we can work out how many object + /// collisions occured using the _perloopcontact if stats collection is enabled. + /// + private int m_tempAvatarCollisionsThisFrame; + + /// + /// Used in calculating physics frame time dilation + /// + private int tickCountFrameRun; + + /// + /// Used in calculating physics frame time dilation + /// + private int latertickcount; + private Random fluidRandomizer = new Random(Environment.TickCount); private const uint m_regionWidth = Constants.RegionSize; @@ -345,9 +420,6 @@ namespace OpenSim.Region.Physics.OdePlugin private OdePrim cp1; private OdeCharacter cc2; private OdePrim cp2; - private int tickCountFrameRun; - - private int latertickcount=0; //private int cStartStop = 0; //private string cDictKey = ""; @@ -440,6 +512,8 @@ namespace OpenSim.Region.Physics.OdePlugin // Initialize the mesh plugin public override void Initialise(IMesher meshmerizer, IConfigSource config) { + InitializeExtraStats(); + mesher = meshmerizer; m_config = config; // Defaults @@ -464,6 +538,8 @@ namespace OpenSim.Region.Physics.OdePlugin IConfig physicsconfig = m_config.Configs["ODEPhysicsSettings"]; if (physicsconfig != null) { + CollectStats = physicsconfig.GetBoolean("collect_stats", false); + gravityx = physicsconfig.GetFloat("world_gravityx", 0f); gravityy = physicsconfig.GetFloat("world_gravityy", 0f); gravityz = physicsconfig.GetFloat("world_gravityz", -9.8f); @@ -764,6 +840,62 @@ namespace OpenSim.Region.Physics.OdePlugin #region Collision Detection + /// + /// Collides two geometries. + /// + /// + /// + /// /param> + /// + /// + /// + private int CollideGeoms( + IntPtr geom1, IntPtr geom2, int maxContacts, Ode.NET.d.ContactGeom[] contactsArray, int contactGeomSize) + { + int count; + + lock (OdeScene.UniversalColliderSyncObject) + { + // We do this inside the lock so that we don't count any delay in acquiring it + if (CollectStats) + m_nativeCollisionStartTick = Util.EnvironmentTickCount(); + + count = d.Collide(geom1, geom2, maxContacts, contactsArray, contactGeomSize); + } + + // We do this outside the lock so that any waiting threads aren't held up, though the effect is probably + // negligable + if (CollectStats) + m_stats[ODENativeGeomCollisionFrameMsStatName] + += Util.EnvironmentTickCountSubtract(m_nativeCollisionStartTick); + + return count; + } + + /// + /// Collide two spaces or a space and a geometry. + /// + /// + /// /param> + /// + private void CollideSpaces(IntPtr space1, IntPtr space2, IntPtr data) + { + if (CollectStats) + { + m_inCollisionTiming = true; + m_nativeCollisionStartTick = Util.EnvironmentTickCount(); + } + + d.SpaceCollide2(space1, space2, data, nearCallback); + + if (CollectStats && m_inCollisionTiming) + { + m_stats[ODENativeSpaceCollisionFrameMsStatName] + += Util.EnvironmentTickCountSubtract(m_nativeCollisionStartTick); + m_inCollisionTiming = false; + } + } + /// /// This is our near callback. A geometry is near a body /// @@ -772,6 +904,13 @@ namespace OpenSim.Region.Physics.OdePlugin /// another geometry or space private void near(IntPtr space, IntPtr g1, IntPtr g2) { + if (CollectStats && m_inCollisionTiming) + { + m_stats[ODENativeSpaceCollisionFrameMsStatName] + += Util.EnvironmentTickCountSubtract(m_nativeCollisionStartTick); + m_inCollisionTiming = false; + } + // m_log.DebugFormat("[PHYSICS]: Colliding {0} and {1} in {2}", g1, g2, space); // no lock here! It's invoked from within Simulate(), which is thread-locked @@ -789,7 +928,7 @@ namespace OpenSim.Region.Physics.OdePlugin // contact points in the space try { - d.SpaceCollide2(g1, g2, IntPtr.Zero, nearCallback); + CollideSpaces(g1, g2, IntPtr.Zero); } catch (AccessViolationException) { @@ -832,6 +971,7 @@ namespace OpenSim.Region.Physics.OdePlugin // Figure out how many contact points we have int count = 0; + try { // Colliding Geom To Geom @@ -843,8 +983,7 @@ namespace OpenSim.Region.Physics.OdePlugin if (b1 != IntPtr.Zero && b2 != IntPtr.Zero && d.AreConnectedExcluding(b1, b2, d.JointType.Contact)) return; - lock (OdeScene.UniversalColliderSyncObject) - count = d.Collide(g1, g2, contacts.Length, contacts, d.ContactGeom.SizeOf); + count = CollideGeoms(g1, g2, contacts.Length, contacts, d.ContactGeom.SizeOf); if (count > contacts.Length) m_log.Error("[ODE SCENE]: Got " + count + " contacts when we asked for a maximum of " + contacts.Length); @@ -1113,14 +1252,12 @@ namespace OpenSim.Region.Physics.OdePlugin { _perloopContact.Add(curContact); - // If we're colliding against terrain if (name1 == "Terrain" || name2 == "Terrain") { - // If we're moving if ((p2.PhysicsActorType == (int) ActorTypes.Agent) && (Math.Abs(p2.Velocity.X) > 0.01f || Math.Abs(p2.Velocity.Y) > 0.01f)) { - // Use the movement terrain contact + // Avatar is moving on terrain, use the movement terrain contact AvatarMovementTerrainContact.geom = curContact; if (m_global_contactcount < maxContactsbeforedeath) @@ -1133,7 +1270,7 @@ namespace OpenSim.Region.Physics.OdePlugin { if (p2.PhysicsActorType == (int)ActorTypes.Agent) { - // Use the non moving terrain contact + // Avatar is standing on terrain, use the non moving terrain contact TerrainContact.geom = curContact; if (m_global_contactcount < maxContactsbeforedeath) @@ -1228,13 +1365,11 @@ namespace OpenSim.Region.Physics.OdePlugin } else { - // we're colliding with prim or avatar - // check if we're moving if ((p2.PhysicsActorType == (int)ActorTypes.Agent)) { if ((Math.Abs(p2.Velocity.X) > 0.01f || Math.Abs(p2.Velocity.Y) > 0.01f)) { - // Use the Movement prim contact + // Avatar is moving on a prim, use the Movement prim contact AvatarMovementprimContact.geom = curContact; if (m_global_contactcount < maxContactsbeforedeath) @@ -1245,9 +1380,8 @@ namespace OpenSim.Region.Physics.OdePlugin } else { - // Use the non movement contact + // Avatar is standing still on a prim, use the non movement contact contact.geom = curContact; - _perloopContact.Add(curContact); if (m_global_contactcount < maxContactsbeforedeath) { @@ -1355,7 +1489,7 @@ namespace OpenSim.Region.Physics.OdePlugin break; } } - //m_log.DebugFormat("[Collsion]: Depth {0}", Math.Abs(contact.depth - contactGeom.depth)); + //m_log.DebugFormat("[Collision]: Depth {0}", Math.Abs(contact.depth - contactGeom.depth)); //m_log.DebugFormat("[Collision]: <{0},{1},{2}>", Math.Abs(contactGeom.normal.X - contact.normal.X), Math.Abs(contactGeom.normal.Y - contact.normal.Y), Math.Abs(contactGeom.normal.Z - contact.normal.Z)); } } @@ -1578,7 +1712,7 @@ namespace OpenSim.Region.Physics.OdePlugin // and we'll run it again on all of them. try { - d.SpaceCollide2(space, chr.Shell, IntPtr.Zero, nearCallback); + CollideSpaces(space, chr.Shell, IntPtr.Zero); } catch (AccessViolationException) { @@ -1593,6 +1727,12 @@ namespace OpenSim.Region.Physics.OdePlugin //} } + if (CollectStats) + { + m_tempAvatarCollisionsThisFrame = _perloopContact.Count; + m_stats[ODEAvatarContactsStatsName] += m_tempAvatarCollisionsThisFrame; + } + List removeprims = null; foreach (OdePrim chr in _activeprims) { @@ -1604,7 +1744,7 @@ namespace OpenSim.Region.Physics.OdePlugin { if (space != IntPtr.Zero && chr.prim_geom != IntPtr.Zero && chr.m_taintremove == false) { - d.SpaceCollide2(space, chr.prim_geom, IntPtr.Zero, nearCallback); + CollideSpaces(space, chr.prim_geom, IntPtr.Zero); } else { @@ -1625,6 +1765,9 @@ namespace OpenSim.Region.Physics.OdePlugin } } + if (CollectStats) + m_stats[ODEPrimContactsStatName] += _perloopContact.Count - m_tempAvatarCollisionsThisFrame; + if (removeprims != null) { foreach (OdePrim chr in removeprims) @@ -2683,21 +2826,23 @@ namespace OpenSim.Region.Physics.OdePlugin /// /// This is our main simulate loop + /// + /// /// It's thread locked by a Mutex in the scene. /// It holds Collisions, it instructs ODE to step through the physical reactions /// It moves the objects around in memory /// It calls the methods that report back to the object owners.. (scenepresence, SceneObjectGroup) - /// + /// /// - /// + /// The number of frames simulated over that period. public override float Simulate(float timeStep) { + int startFrameTick = CollectStats ? Util.EnvironmentTickCount() : 0; + int quickStepTick = 0; + if (framecount >= int.MaxValue) framecount = 0; - //if (m_worldOffset != Vector3.Zero) - // return 0; - framecount++; float fps = 0; @@ -2705,7 +2850,7 @@ namespace OpenSim.Region.Physics.OdePlugin float timeLeft = timeStep; //m_log.Info(timeStep.ToString()); -// step_time += timeStep; +// step_time += timeSte // // // If We're loaded down by something else, // // or debugging with the Visual Studio project on pause @@ -2867,9 +3012,15 @@ namespace OpenSim.Region.Physics.OdePlugin // "[PHYSICS]: Collision contacts to process this frame = {0}", m_global_contactcount); m_global_contactcount = 0; - + + if (CollectStats) + quickStepTick = Util.EnvironmentTickCount(); + d.WorldQuickStep(world, ODE_STEPSIZE); + if (CollectStats) + m_stats[ODENativeStepFrameMsStatName] += Util.EnvironmentTickCountSubtract(quickStepTick); + d.JointGroupEmpty(contactgroup); } catch (Exception e) @@ -2937,7 +3088,7 @@ namespace OpenSim.Region.Physics.OdePlugin d.WorldExportDIF(world, fname, physics_logging_append_existing_logfile, prefix); } - latertickcount = Util.EnvironmentTickCount() - tickCountFrameRun; + latertickcount = Util.EnvironmentTickCountSubtract(tickCountFrameRun); // OpenSimulator above does 10 fps. 10 fps = means that the main thread loop and physics // has a max of 100 ms to run theoretically. @@ -2957,6 +3108,9 @@ namespace OpenSim.Region.Physics.OdePlugin tickCountFrameRun = Util.EnvironmentTickCount(); } + if (CollectStats) + m_stats[ODETotalFrameMsStatName] += Util.EnvironmentTickCountSubtract(startFrameTick); + return fps; } @@ -3190,7 +3344,7 @@ namespace OpenSim.Region.Physics.OdePlugin public override bool IsThreaded { // for now we won't be multithreaded - get { return (false); } + get { return false; } } #region ODE Specific Terrain Fixes @@ -3766,26 +3920,19 @@ namespace OpenSim.Region.Physics.OdePlugin public override Dictionary GetTopColliders() { - Dictionary returncolliders = new Dictionary(); - int cnt = 0; + Dictionary topColliders; + lock (_prims) { - foreach (OdePrim prm in _prims) - { - if (prm.CollisionScore > 0) - { - returncolliders.Add(prm.LocalID, prm.CollisionScore); - cnt++; - prm.CollisionScore = 0f; - if (cnt > 25) - { - break; - } - } - } + List orderedPrims = new List(_prims); + orderedPrims.OrderByDescending(p => p.CollisionScore).Take(25); + topColliders = orderedPrims.ToDictionary(p => p.LocalID, p => p.CollisionScore); + + foreach (OdePrim p in _prims) + p.CollisionScore = 0; } - return returncolliders; + return topColliders; } public override bool SupportsRayCast() @@ -3955,5 +4102,39 @@ namespace OpenSim.Region.Physics.OdePlugin ds.SetViewpoint(ref xyz, ref hpr); } #endif + + public override Dictionary GetStats() + { + if (!CollectStats) + return null; + + Dictionary returnStats; + + lock (OdeLock) + { + returnStats = new Dictionary(m_stats); + + returnStats[ODENativeCollisionFrameMsStatName] + = returnStats[ODENativeSpaceCollisionFrameMsStatName] + + returnStats[ODENativeGeomCollisionFrameMsStatName]; + + InitializeExtraStats(); + } + + return returnStats; + } + + private void InitializeExtraStats() + { + // No need to zero since this is calculated by addition + // m_stats[ODENativeCollisionFrameMsStatName] = 0; + + m_stats[ODETotalFrameMsStatName] = 0; + m_stats[ODENativeStepFrameMsStatName] = 0; + m_stats[ODENativeSpaceCollisionFrameMsStatName] = 0; + m_stats[ODENativeGeomCollisionFrameMsStatName] = 0; + m_stats[ODEAvatarContactsStatsName] = 0; + m_stats[ODEPrimContactsStatName] = 0; + } } } \ No newline at end of file diff --git a/bin/OpenSimDefaults.ini b/bin/OpenSimDefaults.ini index 5da3ba2c9b..27d86e8f8c 100644 --- a/bin/OpenSimDefaults.ini +++ b/bin/OpenSimDefaults.ini @@ -675,6 +675,25 @@ [ODEPhysicsSettings] + ; ## + ; ## Physics stats settings + ; + + ; If collect_stats is enabled, then extra stat information is collected which is accessible via the MonitorModule + ; (see http://opensimulator.org/wiki/Monitoring_Module for more details). + collect_stats = false + + ; ## + ; ## Physics logging settings - logfiles are saved to *.DIF files + ; ## + + ; default is false + ;physics_logging = true + ;; every n simulation iterations, the physics snapshot file is updated + ;physics_logging_interval = 50 + ;; append to existing physics logfile, or overwrite existing logfiles? + ;physics_logging_append_existing_logfile = true + ;## ;## World Settings ;## @@ -823,17 +842,6 @@ ; number^2 physical level of detail of the sculpt texture. 16x16 - 256 verticies mesh_physical_lod = 16 - ; ## - ; ## Physics logging settings - logfiles are saved to *.DIF files - ; ## - - ; default is false - ;physics_logging = true - ;; every n simulation iterations, the physics snapshot file is updated - ;physics_logging_interval = 50 - ;; append to existing physics logfile, or overwrite existing logfiles? - ;physics_logging_append_existing_logfile = true - ; ## ; ## Joint support ; ##