/*
* 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 copyrightD
* 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 log4net;
using Nini.Config;
using Mono.Addins;
using OpenMetaverse;
using OpenSim.Framework;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Servers.HttpServer;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Region.Framework.Scenes;
using Caps=OpenSim.Framework.Capabilities.Caps;
namespace OpenSim.Region.CoreModules.Framework
{
[Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "CapabilitiesModule")]
public class CapabilitiesModule : INonSharedRegionModule, ICapabilitiesModule
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly string m_showCapsCommandFormat = " {0,-38} {1,-60}\n";
protected Scene m_scene;
///
/// Each agent has its own capabilities handler.
///
protected readonly Dictionary m_capsObjects = new();
protected readonly Dictionary m_capsPaths = new();
protected readonly Dictionary> m_childrenSeeds = new();
public void Initialise(IConfigSource source)
{
}
public void AddRegion(Scene scene)
{
m_scene = scene;
m_scene.RegisterModuleInterface(this);
MainConsole.Instance.Commands.AddCommand(
"Comms", false, "show caps list",
"show caps list",
"Shows list of registered capabilities for users.", HandleShowCapsListCommand);
MainConsole.Instance.Commands.AddCommand(
"Comms", false, "show caps stats by user",
"show caps stats by user [ ]",
"Shows statistics on capabilities use by user.",
"If a user name is given, then prints a detailed breakdown of caps use ordered by number of requests received.",
HandleShowCapsStatsByUserCommand);
MainConsole.Instance.Commands.AddCommand(
"Comms", false, "show caps stats by cap",
"show caps stats by cap []",
"Shows statistics on capabilities use by capability.",
"If a capability name is given, then prints a detailed breakdown of use by each user.",
HandleShowCapsStatsByCapCommand);
}
public void RegionLoaded(Scene scene)
{
}
public void RemoveRegion(Scene scene)
{
m_scene.UnregisterModuleInterface(this);
}
public void PostInitialise()
{
}
public void Close() {}
public string Name
{
get { return "Capabilities Module"; }
}
public Type ReplaceableInterface
{
get { return null; }
}
public void CreateCaps(UUID agentId, uint circuitCode)
{
string capsObjectPath = GetCapsPath(agentId);
Caps caps;
lock (m_capsObjects)
{
if (m_capsObjects.TryGetValue(circuitCode, out Caps oldCaps))
{
if (capsObjectPath == oldCaps.CapsObjectPath)
{
//m_log.WarnFormat(
// "[CAPS]: Reusing caps for agent {0} in region {1}. Old caps path {2}, new caps path {3}. ",
// agentId, m_scene.RegionInfo.RegionName, oldCaps.CapsObjectPath, capsObjectPath);
return;
}
else
{
// not reusing add extra melanie cleanup
// Remove tge handlers. They may conflict with the
// new object created below
oldCaps.DeregisterHandlers();
// Better safe ... should not be needed but also
// no big deal
m_capsObjects.Remove(circuitCode);
}
}
//m_log.DebugFormat(
// "[CAPS]: Adding capabilities for agent {0} in {1} with path {2}",
// agentId, m_scene.RegionInfo.RegionName, capsObjectPath);
caps = new Caps(MainServer.Instance, m_scene.RegionInfo.ExternalHostName,
(MainServer.Instance is null) ? 0: MainServer.Instance.Port,
capsObjectPath, agentId, m_scene.RegionInfo.RegionName);
m_log.Debug($"[CreateCaps]: new caps agent {agentId}, circuit {circuitCode}, path {caps.CapsObjectPath}");
m_capsObjects[circuitCode] = caps;
}
m_scene.EventManager.TriggerOnRegisterCaps(agentId, caps);
}
public void RemoveCaps(UUID agentId, uint circuitCode)
{
m_log.DebugFormat("[CAPS]: Remove caps for agent {0} in region {1}", agentId, m_scene.RegionInfo.RegionName);
lock (m_childrenSeeds)
{
m_childrenSeeds.Remove(agentId);
}
lock (m_capsObjects)
{
if (m_capsObjects.TryGetValue(circuitCode, out Caps cp))
{
m_scene.EventManager.TriggerOnDeregisterCaps(agentId, cp);
m_capsObjects.Remove(circuitCode);
cp.Dispose();
}
else
{
foreach (KeyValuePair kvp in m_capsObjects)
{
if (agentId.Equals(kvp.Value.AgentID))
{
m_scene.EventManager.TriggerOnDeregisterCaps(agentId, kvp.Value);
m_capsObjects.Remove(kvp.Key);
kvp.Value.Dispose();
return;
}
}
m_log.WarnFormat(
"[CAPS]: Received request to remove CAPS handler for root agent {0} in {1}, but no such CAPS handler found!",
agentId, m_scene.RegionInfo.RegionName);
}
}
}
public Caps GetCapsForUser(uint circuitCode)
{
lock (m_capsObjects)
{
if (m_capsObjects.TryGetValue(circuitCode, out Caps cp))
return cp;
}
return null;
}
public void ActivateCaps(uint circuitCode)
{
lock (m_capsObjects)
{
if (m_capsObjects.TryGetValue(circuitCode, out Caps cp))
cp.Activate();
}
}
public void SetAgentCapsSeeds(AgentCircuitData agent)
{
lock (m_capsPaths)
m_capsPaths[agent.AgentID] = agent.CapsPath;
lock (m_childrenSeeds)
m_childrenSeeds[agent.AgentID] = (agent.ChildrenCapSeeds ?? new Dictionary());
}
public string GetCapsPath(UUID agentId)
{
lock (m_capsPaths)
{
if (m_capsPaths.TryGetValue(agentId, out string path))
return path;
}
return null;
}
public Dictionary GetChildrenSeeds(UUID agentID)
{
lock (m_childrenSeeds)
{
if (m_childrenSeeds.TryGetValue(agentID, out Dictionary seeds))
return seeds;
}
return new Dictionary();
}
public void DropChildSeed(UUID agentID, ulong handle)
{
lock (m_childrenSeeds)
{
if (m_childrenSeeds.TryGetValue(agentID, out Dictionary seeds))
{
seeds.Remove(handle);
}
}
}
public string GetChildSeed(UUID agentID, ulong handle)
{
lock (m_childrenSeeds)
{
if (m_childrenSeeds.TryGetValue(agentID, out Dictionary seeds))
{
if (seeds.TryGetValue(handle, out string returnval))
return returnval;
}
}
return null;
}
public void SetChildrenSeed(UUID agentID, Dictionary seeds)
{
//m_log.DebugFormat(" !!! Setting child seeds in {0} to {1}", m_scene.RegionInfo.RegionName, seeds.Count);
lock (m_childrenSeeds)
m_childrenSeeds[agentID] = seeds;
}
public void DumpChildrenSeeds(UUID agentID)
{
m_log.Info("================ ChildrenSeed "+m_scene.RegionInfo.RegionName+" ================");
lock (m_childrenSeeds)
{
foreach (KeyValuePair kvp in m_childrenSeeds[agentID])
{
Util.RegionHandleToRegionLoc(kvp.Key, out uint x, out uint y);
m_log.Info(" >> "+x+", "+y+": "+kvp.Value);
}
}
}
private void HandleShowCapsListCommand(string module, string[] cmdParams)
{
if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_scene)
return;
StringBuilder capsReport = new();
capsReport.AppendFormat("Region {0}:\n", m_scene.RegionInfo.RegionName);
lock (m_capsObjects)
{
foreach (KeyValuePair kvp in m_capsObjects)
{
Caps caps = kvp.Value;
string name = string.Empty;
if(m_scene.TryGetScenePresence(caps.AgentID, out ScenePresence sp) && sp!=null)
name = sp.Name;
capsReport.AppendFormat("** Circuit {0}; {1} {2}:\n", kvp.Key, caps.AgentID,name);
capsReport.AppendFormat("** Base URL {0}\n", caps.CapsHandlers.BaseURL);
Dictionary capsPaths = caps.CapsHandlers.GetCapsLocalPaths();
foreach(KeyValuePair kvp2 in capsPaths)
capsReport.AppendFormat(m_showCapsCommandFormat, kvp2.Key, kvp2.Value);
foreach (KeyValuePair kvp2 in caps.GetPollHandlers())
capsReport.AppendFormat(m_showCapsCommandFormat, kvp2.Key, kvp2.Value.Url);
foreach (KeyValuePair kvp3 in caps.ExternalCapsHandlers)
capsReport.AppendFormat(m_showCapsCommandFormat, kvp3.Key, kvp3.Value);
}
}
MainConsole.Instance.Output(capsReport.ToString());
}
private void HandleShowCapsStatsByCapCommand(string module, string[] cmdParams)
{
if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_scene)
return;
if (cmdParams.Length != 5 && cmdParams.Length != 6)
{
MainConsole.Instance.Output("Usage: show caps stats by cap []");
return;
}
StringBuilder sb = new StringBuilder();
sb.AppendFormat("Region {0}:\n", m_scene.Name);
if (cmdParams.Length == 5)
{
BuildSummaryStatsByCapReport(sb);
}
else if (cmdParams.Length == 6)
{
BuildDetailedStatsByCapReport(sb, cmdParams[5]);
}
MainConsole.Instance.Output(sb.ToString());
}
private void BuildDetailedStatsByCapReport(StringBuilder sb, string capName)
{
/*
sb.AppendFormat("Capability name {0}\n", capName);
ConsoleDisplayTable cdt = new ConsoleDisplayTable();
cdt.AddColumn("User Name", 34);
cdt.AddColumn("Req Received", 12);
cdt.AddColumn("Req Handled", 12);
cdt.Indent = 2;
Dictionary receivedStats = new Dictionary();
Dictionary handledStats = new Dictionary();
m_scene.ForEachScenePresence(
sp =>
{
Caps caps = m_scene.CapsModule.GetCapsForUser(sp.UUID);
if (caps == null)
return;
Dictionary capsHandlers = caps.CapsHandlers.GetCapsHandlers();
IRequestHandler reqHandler;
if (capsHandlers.TryGetValue(capName, out reqHandler))
{
receivedStats[sp.Name] = reqHandler.RequestsReceived;
handledStats[sp.Name] = reqHandler.RequestsHandled;
}
else
{
PollServiceEventArgs pollHandler = null;
if (caps.TryGetPollHandler(capName, out pollHandler))
{
receivedStats[sp.Name] = pollHandler.RequestsReceived;
handledStats[sp.Name] = pollHandler.RequestsHandled;
}
}
}
);
foreach (KeyValuePair kvp in receivedStats.OrderByDescending(kp => kp.Value))
{
cdt.AddRow(kvp.Key, kvp.Value, handledStats[kvp.Key]);
}
sb.Append(cdt.ToString());
*/
}
private void BuildSummaryStatsByCapReport(StringBuilder sb)
{
/*
ConsoleDisplayTable cdt = new ConsoleDisplayTable();
cdt.AddColumn("Name", 34);
cdt.AddColumn("Req Received", 12);
cdt.AddColumn("Req Handled", 12);
cdt.Indent = 2;
Dictionary receivedStats = new Dictionary();
Dictionary handledStats = new Dictionary();
m_scene.ForEachScenePresence(
sp =>
{
Caps caps = m_scene.CapsModule.GetCapsForUser(sp.UUID);
if (caps == null)
return;
foreach (IRequestHandler reqHandler in caps.CapsHandlers.GetCapsHandlers().Values)
{
string reqName = reqHandler.Name ?? "";
if (!receivedStats.ContainsKey(reqName))
{
receivedStats[reqName] = reqHandler.RequestsReceived;
handledStats[reqName] = reqHandler.RequestsHandled;
}
else
{
receivedStats[reqName] += reqHandler.RequestsReceived;
handledStats[reqName] += reqHandler.RequestsHandled;
}
}
foreach (KeyValuePair kvp in caps.GetPollHandlers())
{
string name = kvp.Key;
PollServiceEventArgs pollHandler = kvp.Value;
if (!receivedStats.ContainsKey(name))
{
receivedStats[name] = pollHandler.RequestsReceived;
handledStats[name] = pollHandler.RequestsHandled;
}
else
{
receivedStats[name] += pollHandler.RequestsReceived;
handledStats[name] += pollHandler.RequestsHandled;
}
}
}
);
foreach (KeyValuePair kvp in receivedStats.OrderByDescending(kp => kp.Value))
cdt.AddRow(kvp.Key, kvp.Value, handledStats[kvp.Key]);
sb.Append(cdt.ToString());
*/
}
private void HandleShowCapsStatsByUserCommand(string module, string[] cmdParams)
{
/*
if (SceneManager.Instance.CurrentScene != null && SceneManager.Instance.CurrentScene != m_scene)
return;
if (cmdParams.Length != 5 && cmdParams.Length != 7)
{
MainConsole.Instance.Output("Usage: show caps stats by user [ ]");
return;
}
StringBuilder sb = new StringBuilder();
sb.AppendFormat("Region {0}:\n", m_scene.Name);
if (cmdParams.Length == 5)
{
BuildSummaryStatsByUserReport(sb);
}
else if (cmdParams.Length == 7)
{
string firstName = cmdParams[5];
string lastName = cmdParams[6];
ScenePresence sp = m_scene.GetScenePresence(firstName, lastName);
if (sp == null)
return;
BuildDetailedStatsByUserReport(sb, sp);
}
MainConsole.Instance.Output(sb.ToString());
*/
}
private void BuildDetailedStatsByUserReport(StringBuilder sb, ScenePresence sp)
{
/*
sb.AppendFormat("Avatar name {0}, type {1}\n", sp.Name, sp.IsChildAgent ? "child" : "root");
ConsoleDisplayTable cdt = new ConsoleDisplayTable();
cdt.AddColumn("Cap Name", 34);
cdt.AddColumn("Req Received", 12);
cdt.AddColumn("Req Handled", 12);
cdt.Indent = 2;
Caps caps = m_scene.CapsModule.GetCapsForUser(sp.UUID);
if (caps == null)
return;
List capRows = new List();
foreach (IRequestHandler reqHandler in caps.CapsHandlers.GetCapsHandlers().Values)
capRows.Add(new CapTableRow(reqHandler.Name, reqHandler.RequestsReceived, reqHandler.RequestsHandled));
foreach (KeyValuePair kvp in caps.GetPollHandlers())
capRows.Add(new CapTableRow(kvp.Key, kvp.Value.RequestsReceived, kvp.Value.RequestsHandled));
foreach (CapTableRow ctr in capRows.OrderByDescending(ctr => ctr.RequestsReceived))
cdt.AddRow(ctr.Name, ctr.RequestsReceived, ctr.RequestsHandled);
sb.Append(cdt.ToString());
*/
}
private void BuildSummaryStatsByUserReport(StringBuilder sb)
{
/*
ConsoleDisplayTable cdt = new ConsoleDisplayTable();
cdt.AddColumn("Name", 32);
cdt.AddColumn("Type", 5);
cdt.AddColumn("Req Received", 12);
cdt.AddColumn("Req Handled", 12);
cdt.Indent = 2;
m_scene.ForEachScenePresence(
sp =>
{
Caps caps = m_scene.CapsModule.GetCapsForUser(sp.UUID);
if (caps == null)
return;
Dictionary capsHandlers = caps.CapsHandlers.GetCapsHandlers();
int totalRequestsReceived = 0;
int totalRequestsHandled = 0;
foreach (IRequestHandler reqHandler in capsHandlers.Values)
{
totalRequestsReceived += reqHandler.RequestsReceived;
totalRequestsHandled += reqHandler.RequestsHandled;
}
Dictionary capsPollHandlers = caps.GetPollHandlers();
foreach (PollServiceEventArgs handler in capsPollHandlers.Values)
{
totalRequestsReceived += handler.RequestsReceived;
totalRequestsHandled += handler.RequestsHandled;
}
cdt.AddRow(sp.Name, sp.IsChildAgent ? "child" : "root", totalRequestsReceived, totalRequestsHandled);
}
);
sb.Append(cdt.ToString());
*/
}
private class CapTableRow
{
public string Name { get; set; }
public int RequestsReceived { get; set; }
public int RequestsHandled { get; set; }
public CapTableRow(string name, int requestsReceived, int requestsHandled)
{
Name = name;
RequestsReceived = requestsReceived;
RequestsHandled = requestsHandled;
}
}
}
}