Compare commits

...
Sign in to create a new pull request.

5 commits

Author SHA1 Message Date
Leath Cooper
7b32501f19 OTEL .NET Runtime Instrumentation 2024-11-16 12:02:07 -05:00
Leath Cooper
677f4b1163 Merge branch 'main' into feat-telemetry 2024-11-16 11:21:48 -05:00
Leath Cooper
2acfc1d82d experimental metrics 2024-10-30 20:14:43 -04:00
Leath Cooper
6e49f60fbc additional metrics 2024-10-26 08:17:02 -04:00
Leath Cooper
56ea91b890 OTEL POC 2024-10-21 10:34:35 -04:00
19 changed files with 274 additions and 16 deletions

2
.gitignore vendored
View file

@ -42,3 +42,5 @@
/packages/* /packages/*
/Distribution/Configuration/server-access.json /Distribution/Configuration/server-access.json
/grafana-storage/*

View file

@ -420,6 +420,8 @@ public static class Core
VerifySerialization(); VerifySerialization();
Telemetry.Start();
_now = DateTime.UtcNow; _now = DateTime.UtcNow;
_firstTick = _tickCount = GetTimestamp(); _firstTick = _tickCount = GetTimestamp();

View file

@ -28,6 +28,7 @@ using Server.Targeting;
using Server.Text; using Server.Text;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.Metrics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using CalcMoves = Server.Movement.Movement; using CalcMoves = Server.Movement.Movement;
@ -196,6 +197,10 @@ public delegate int AOSStatusHandler(Mobile from, int index);
/// </summary> /// </summary>
public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPropertyListEntity, IValueLinkListNode<Mobile> public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPropertyListEntity, IValueLinkListNode<Mobile>
{ {
private static readonly Counter<long> mobilesCreatedCounter = Telemetry.MobilesMeter.CreateCounter<long>("mobiles_created_count");
private static readonly Counter<long> mobilesDeletedCounter = Telemetry.MobilesMeter.CreateCounter<long>("mobiles_deleted_count");
private static readonly Counter<long> mobilesKilledCounter = Telemetry.MobilesMeter.CreateCounter<long>("mobiles_killed_count");
// Allow four warmode changes in 0.5 seconds, any more will be delay for two seconds // Allow four warmode changes in 0.5 seconds, any more will be delay for two seconds
private const int WarmodeCatchCount = 4; private const int WarmodeCatchCount = 4;
@ -364,6 +369,11 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
DefaultMobileInit(); DefaultMobileInit();
World.AddEntity(this); World.AddEntity(this);
mobilesCreatedCounter.Add(1, new KeyValuePair<string, object?>[]
{
new("Map", Map?.Name),
});
} }
public Mobile(Serial serial) public Mobile(Serial serial)
@ -2419,6 +2429,14 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
DropHolding(); DropHolding();
if (Alive) {
mobilesDeletedCounter.Add(1, new KeyValuePair<string, object?>[]
{
new("Map", Map?.Name),
new("Region", Region?.Name),
});
}
Region.OnRegionChange(this, m_Region, null); Region.OnRegionChange(this, m_Region, null);
m_Region = null; m_Region = null;
@ -4801,6 +4819,13 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
} }
} }
mobilesKilledCounter.Add(1, new KeyValuePair<string, object?>[]
{
new("Map", Map?.Name),
new("Region", Region?.Name),
new("IsPlayer", Player),
});
Region.OnDeath(this); Region.OnDeath(this);
OnDeath(c); OnDeath(c);
} }

View file

@ -23,6 +23,7 @@ using System;
using System.Buffers; using System.Buffers;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.Metrics;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
@ -37,6 +38,8 @@ public delegate int EncodePacket(ReadOnlySpan<byte> inputBuffer, Span<byte> outp
public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetState> public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetState>
{ {
private static readonly Counter<long> packetCounter =
Telemetry.NetworkMeter.CreateCounter<long>("net_state_packet_counter");
private static readonly ILogger logger = LogFactory.GetLogger(typeof(NetState)); private static readonly ILogger logger = LogFactory.GetLogger(typeof(NetState));
private static readonly TimeSpan ConnectingSocketIdleLimit = TimeSpan.FromMilliseconds(5000); // 5 seconds private static readonly TimeSpan ConnectingSocketIdleLimit = TimeSpan.FromMilliseconds(5000); // 5 seconds
@ -476,6 +479,12 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
LogPacket(span, false); LogPacket(span, false);
} }
packetCounter.Add(1, new KeyValuePair<string, object>[]
{
new("PacketID", span[0]),
new("Kind", "SERVER"),
});
SendPipe.Writer.Advance((uint)length); SendPipe.Writer.Advance((uint)length);
if (!_flushQueued) if (!_flushQueued)
@ -564,6 +573,11 @@ public partial class NetState : IComparable<NetState>, IValueLinkListNode<NetSta
var packetId = packetReader.ReadByte(); var packetId = packetReader.ReadByte();
int packetLength = length; int packetLength = length;
packetCounter.Add(1, new KeyValuePair<string, object>[]
{
new("PacketID", packetId),
new("Kind", "CLIENT"),
});
// These can arrive at any time and are only informational // These can arrive at any time and are only informational
if (_protocolState != ProtocolState.AwaitingSeed && IncomingPackets.IsInfoPacket(packetId)) if (_protocolState != ProtocolState.AwaitingSeed && IncomingPackets.IsInfoPacket(packetId))
{ {

View file

@ -14,6 +14,7 @@
*************************************************************************/ *************************************************************************/
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.Metrics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
@ -27,6 +28,18 @@ namespace Server.Network;
public static class TcpServer public static class TcpServer
{ {
private static readonly Counter<long> attemptedConnectionsCounter =
Telemetry.NetworkMeter.CreateCounter<long>("tcp_server_attempted_connections_count");
private static readonly Counter<long> ipLimitedConnectionsCounter =
Telemetry.NetworkMeter.CreateCounter<long>("tcp_server_ip_limited_connections_count");
private static readonly Counter<long> firewalledConnectionsCounter =
Telemetry.NetworkMeter.CreateCounter<long>("tcp_server_firewalled_connections_count");
private static readonly Counter<long> allowedConnectionsCounter =
Telemetry.NetworkMeter.CreateCounter<long>("tcp_server_allowed_connections_count");
private static readonly ILogger logger = LogFactory.GetLogger(typeof(TcpServer)); private static readonly ILogger logger = LogFactory.GetLogger(typeof(TcpServer));
// AccountLoginReject BadComm // AccountLoginReject BadComm
@ -78,8 +91,11 @@ public static class TcpServer
} }
public static IEnumerable<IPEndPoint> GetListeningAddresses(IPEndPoint ipep) => public static IEnumerable<IPEndPoint> GetListeningAddresses(IPEndPoint ipep) =>
NetworkInterface.GetAllNetworkInterfaces().SelectMany(adapter => NetworkInterface.GetAllNetworkInterfaces()
adapter.GetIPProperties().UnicastAddresses .SelectMany(
adapter =>
adapter.GetIPProperties()
.UnicastAddresses
.Where(uip => ipep.AddressFamily == uip.Address.AddressFamily) .Where(uip => ipep.AddressFamily == uip.Address.AddressFamily)
.Select(uip => new IPEndPoint(uip.Address, ipep.Port)) .Select(uip => new IPEndPoint(uip.Address, ipep.Port))
); );
@ -133,13 +149,17 @@ public static class TcpServer
socket = await listener.AcceptAsync(); socket = await listener.AcceptAsync();
var remoteIP = ((IPEndPoint)socket.RemoteEndPoint)!.Address; var remoteIP = ((IPEndPoint)socket.RemoteEndPoint)!.Address;
attemptedConnectionsCounter.Add(1);
if (!IPLimiter.Verify(remoteIP)) if (!IPLimiter.Verify(remoteIP))
{ {
ipLimitedConnectionsCounter.Add(1);
TraceDisconnect("Past IP limit threshold", remoteIP); TraceDisconnect("Past IP limit threshold", remoteIP);
logger.Debug("{Address} Past IP limit threshold", remoteIP); logger.Debug("{Address} Past IP limit threshold", remoteIP);
} }
else if (Firewall.IsBlocked(remoteIP)) else if (Firewall.IsBlocked(remoteIP))
{ {
firewalledConnectionsCounter.Add(1);
TraceDisconnect("Firewalled", remoteIP); TraceDisconnect("Firewalled", remoteIP);
logger.Debug("{Address} Firewalled", remoteIP); logger.Debug("{Address} Firewalled", remoteIP);
} }
@ -150,6 +170,7 @@ public static class TcpServer
if (args.AllowConnection) if (args.AllowConnection)
{ {
allowedConnectionsCounter.Add(1);
_ = new NetState(socket); _ = new NetState(socket);
continue; continue;
} }

View file

@ -35,6 +35,9 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="CommunityToolkit.HighPerformance" Version="8.3.2" /> <PackageReference Include="CommunityToolkit.HighPerformance" Version="8.3.2" />
<PackageReference Include="LibDeflate.Bindings" Version="1.0.2.120" /> <PackageReference Include="LibDeflate.Bindings" Version="1.0.2.120" />
<PackageReference Include="OpenTelemetry" Version="1.10.0-beta.1" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.10.0-beta.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.9.0" />
<PackageReference Include="PollGroup" Version="1.5.1" /> <PackageReference Include="PollGroup" Version="1.5.1" />
<PackageReference Include="System.IO.Hashing" Version="8.0.0" /> <PackageReference Include="System.IO.Hashing" Version="8.0.0" />

View file

@ -0,0 +1,39 @@
using System;
using System.Diagnostics.Metrics;
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
namespace Server;
public class Telemetry
{
public static MeterProvider MeterProvider { get; private set; }
public static readonly Meter AccountingMeter = new("ModernUO.Accounting");
public static readonly Meter ItemsMeter = new("ModernUO.Items");
public static readonly Meter MobilesMeter = new("ModernUO.Mobiles");
public static readonly Meter NetworkMeter = new("ModernUO.Network");
public static readonly Meter EconomyMeter = new("ModernUO.Economy");
public static void Start()
{
MeterProvider = Sdk.CreateMeterProviderBuilder()
.ConfigureResource(r => r.AddService("ModernUO"))
.AddRuntimeInstrumentation()
.AddOtlpExporter(options =>
{
options.BatchExportProcessorOptions.ScheduledDelayMilliseconds = TimeSpan.FromSeconds(20.0).Milliseconds;
})
.AddMeter("ModernUO.Accounting")
.AddMeter("ModernUO.Items")
.AddMeter("ModernUO.Mobiles")
.AddMeter("ModernUO.Economy")
.AddMeter("ModernUO.Network")
.AddView(
"gold_created_histogram",
new ExplicitBucketHistogramConfiguration()
{ Boundaries = new double[] { 100, 200, 300, 400, 500, 700, 1000, 1500, 2000 } }
)
.Build();
}
}

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.Metrics;
using System.Net; using System.Net;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Xml; using System.Xml;
@ -15,6 +16,9 @@ namespace Server.Accounting;
[SerializationGenerator(6)] [SerializationGenerator(6)]
public partial class Account : IAccount, IComparable<Account> public partial class Account : IAccount, IComparable<Account>
{ {
private static readonly Counter<long> accountsCreatedCounter = Telemetry.AccountingMeter.CreateCounter<long>("accounts_created_count");
private static readonly Counter<long> loginsCounter = Telemetry.AccountingMeter.CreateCounter<long>("logins_count");
public static readonly TimeSpan YoungDuration = TimeSpan.FromHours(40.0); public static readonly TimeSpan YoungDuration = TimeSpan.FromHours(40.0);
public static readonly TimeSpan InactiveDuration = TimeSpan.FromDays(180.0); public static readonly TimeSpan InactiveDuration = TimeSpan.FromDays(180.0);
public static readonly TimeSpan EmptyInactiveDuration = TimeSpan.FromDays(30.0); public static readonly TimeSpan EmptyInactiveDuration = TimeSpan.FromDays(30.0);
@ -133,6 +137,8 @@ public partial class Account : IAccount, IComparable<Account>
Accounts.Add(this); Accounts.Add(this);
this.MarkDirty(); this.MarkDirty();
accountsCreatedCounter.Add(1);
} }
public Account(XmlElement node) public Account(XmlElement node)
@ -794,6 +800,8 @@ public partial class Account : IAccount, IComparable<Account>
m.SendAsciiMessage($"You will enjoy the benefits and relatively safe status of a young player for {hours} more hours."); m.SendAsciiMessage($"You will enjoy the benefits and relatively safe status of a young player for {hours} more hours.");
} }
} }
loginsCounter.Add(1, new KeyValuePair<string, object>("AccessLevel", $"{pm.AccessLevel}"));
} }
public void RemoveYoungStatus(int message) public void RemoveYoungStatus(int message)

View file

@ -1,4 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics.Metrics;
using Server.Items; using Server.Items;
using Server.Targeting; using Server.Targeting;
@ -6,6 +8,8 @@ namespace Server.Engines.Harvest
{ {
public abstract class HarvestSystem public abstract class HarvestSystem
{ {
private static readonly Counter<long> harvestedResourcesCounter =
Telemetry.EconomyMeter.CreateCounter<long>("harvested_resources_count");
public HarvestDefinition[] Definitions { get; init; } public HarvestDefinition[] Definitions { get; init; }
public virtual bool CheckTool(Mobile from, Item tool) public virtual bool CheckTool(Mobile from, Item tool)
@ -196,6 +200,13 @@ namespace Server.Engines.Harvest
bank.Consume(item.Amount, from); bank.Consume(item.Amount, from);
harvestedResourcesCounter.Add(item.Amount, new KeyValuePair<string, object>[]
{
new("Resource", item.GetType().Name),
new("Map", map?.Name),
new("Region", from?.Region.Name),
});
if (Give(from, item, def.PlaceAtFeetIfFull)) if (Give(from, item, def.PlaceAtFeetIfFull))
{ {
SendSuccessTo(from, item, resource); SendSuccessTo(from, item, resource);

View file

@ -1,8 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.Metrics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Server.Collections; using Server.Collections;
using Server.Ethics;
using Server.Logging; using Server.Logging;
using Server.Mobiles; using Server.Mobiles;
@ -10,6 +12,7 @@ namespace Server.Engines.PlayerMurderSystem;
public class PlayerMurderSystem : GenericPersistence public class PlayerMurderSystem : GenericPersistence
{ {
private static readonly Counter<long> playerMurderCounter = Telemetry.MobilesMeter.CreateCounter<long>("player_murder_count");
private static PlayerMurderSystem _playerMurderPersistence; private static PlayerMurderSystem _playerMurderPersistence;
private static readonly ILogger logger = LogFactory.GetLogger(typeof(PlayerMurderSystem)); private static readonly ILogger logger = LogFactory.GetLogger(typeof(PlayerMurderSystem));
@ -166,6 +169,12 @@ public class PlayerMurderSystem : GenericPersistence
public static void OnPlayerMurder(PlayerMobile player) public static void OnPlayerMurder(PlayerMobile player)
{ {
playerMurderCounter.Add(1, new KeyValuePair<string, object>[]
{
new("Map", player.Map?.Name),
new("Region", player.Region?.Name),
});
var context = GetOrCreateMurderContext(player); var context = GetOrCreateMurderContext(player);
context.ShortTermMurders++; context.ShortTermMurders++;
player.Kills++; player.Kills++;

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.Metrics;
using System.Reflection; using System.Reflection;
using System.Text.Json; using System.Text.Json;
using ModernUO.Serialization; using ModernUO.Serialization;
@ -15,6 +16,9 @@ namespace Server.Engines.Spawners;
[SerializationGenerator(10, false)] [SerializationGenerator(10, false)]
public abstract partial class BaseSpawner : Item, ISpawner public abstract partial class BaseSpawner : Item, ISpawner
{ {
private static readonly Counter<long> mobilesSpawnedCounter = Telemetry.MobilesMeter.CreateCounter<long>("mobiles_spawned_count");
private static readonly Counter<long> itemsSpawnedCounter = Telemetry.ItemsMeter.CreateCounter<long>("items_spawned_count");
[SerializedIgnoreDupe] [SerializedIgnoreDupe]
[SerializableField(0)] [SerializableField(0)]
[SerializedCommandProperty(AccessLevel.Developer)] [SerializedCommandProperty(AccessLevel.Developer)]
@ -703,6 +707,12 @@ public abstract partial class BaseSpawner : Item, ISpawner
m.Spawner = this; m.Spawner = this;
m.OnAfterSpawn(); m.OnAfterSpawn();
mobilesSpawnedCounter.Add(1, new KeyValuePair<string, object?>[]
{
new("Map", map),
new("Region", m.Region),
});
} }
else if (entity is Item item) else if (entity is Item item)
{ {
@ -717,6 +727,8 @@ public abstract partial class BaseSpawner : Item, ISpawner
item.Spawner = this; item.Spawner = this;
item.OnAfterSpawn(); item.OnAfterSpawn();
itemsSpawnedCounter.Add(1, new KeyValuePair<string, object?>("Map", map));
} }
else else
{ {

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics.Metrics;
using ModernUO.Serialization; using ModernUO.Serialization;
using Server.Accounting; using Server.Accounting;
@ -7,6 +8,10 @@ namespace Server.Items;
[SerializationGenerator(0, false)] [SerializationGenerator(0, false)]
public partial class Gold : Item public partial class Gold : Item
{ {
private static readonly Counter<long> goldCreatedCounter = Telemetry.EconomyMeter.CreateCounter<long>("gold_created_count");
private static readonly Histogram<long> goldCreatedHistogram = Telemetry.EconomyMeter.CreateHistogram<long>("gold_created_histogram");
private static readonly Counter<long> goldDeletedCounter = Telemetry.EconomyMeter.CreateCounter<long>("gold_deleted_count");
[Constructible] [Constructible]
public Gold(int amountFrom, int amountTo) : this(Utility.RandomMinMax(amountFrom, amountTo)) public Gold(int amountFrom, int amountTo) : this(Utility.RandomMinMax(amountFrom, amountTo))
{ {
@ -36,6 +41,19 @@ public partial class Gold : Item
var newValue = Amount; var newValue = Amount;
UpdateTotal(this, TotalType.Gold, newValue - oldValue); UpdateTotal(this, TotalType.Gold, newValue - oldValue);
if (newValue > 1)
{
goldCreatedCounter.Add(newValue);
goldCreatedHistogram.Record(newValue);
}
}
public override void Delete()
{
goldDeletedCounter.Add(Amount);
base.Delete();
} }
public override void OnAdded(IEntity parent) public override void OnAdded(IEntity parent)

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.Metrics;
using ModernUO.CodeGeneratedEvents; using ModernUO.CodeGeneratedEvents;
using Server.Accounting; using Server.Accounting;
using Server.Collections; using Server.Collections;
@ -99,6 +100,7 @@ namespace Server.Mobiles
public partial class PlayerMobile : Mobile, IHonorTarget, IHasSteps public partial class PlayerMobile : Mobile, IHonorTarget, IHasSteps
{ {
public static readonly Counter<long> playerDeathCounter = Telemetry.MobilesMeter.CreateCounter<long>("player_death_count");
private static bool m_NoRecursion; private static bool m_NoRecursion;
private static readonly Point3D[] m_TrammelDeathDestinations = private static readonly Point3D[] m_TrammelDeathDestinations =
@ -2542,6 +2544,12 @@ namespace Server.Mobiles
base.OnDeath(c); base.OnDeath(c);
playerDeathCounter.Add(1, new KeyValuePair<string, object>[]
{
new("Map", Map?.Name),
new("Region", Region?.Name),
});
EquipSnapshot = null; EquipSnapshot = null;
HueMod = -1; HueMod = -1;

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.Metrics;
using Server.Engines.ConPVP; using Server.Engines.ConPVP;
using Server.Items; using Server.Items;
using Server.Misc; using Server.Misc;
@ -15,6 +16,8 @@ namespace Server.Spells
{ {
public abstract class Spell : ISpell public abstract class Spell : ISpell
{ {
protected static readonly Counter<long> spellCastCount = Telemetry.MobilesMeter.CreateCounter<long>("spell_cast_count");
private static readonly TimeSpan NextSpellDelay = TimeSpan.FromSeconds(0.75); private static readonly TimeSpan NextSpellDelay = TimeSpan.FromSeconds(0.75);
private static readonly TimeSpan AnimateDelay = TimeSpan.FromSeconds(1.5); private static readonly TimeSpan AnimateDelay = TimeSpan.FromSeconds(1.5);
// In reality, it's ANY delayed Damage spell Post-AoS that can't stack, but, only // In reality, it's ANY delayed Damage spell Post-AoS that can't stack, but, only
@ -922,43 +925,52 @@ namespace Server.Spells
private class CastTimer : Timer private class CastTimer : Timer
{ {
private readonly Spell m_Spell; private readonly Spell _spell;
public CastTimer(Spell spell, TimeSpan castDelay) : base(castDelay) public CastTimer(Spell spell, TimeSpan castDelay) : base(castDelay)
{ {
m_Spell = spell; _spell = spell;
} }
protected override void OnTick() protected override void OnTick()
{ {
var caster = m_Spell?.Caster; var caster = _spell?.Caster;
if (caster == null) if (caster == null)
{ {
return; return;
} }
if (m_Spell.State == SpellState.Casting && caster.Spell == m_Spell) if (_spell.State == SpellState.Casting && caster.Spell == _spell)
{ {
m_Spell.State = SpellState.Sequencing; _spell.State = SpellState.Sequencing;
m_Spell._castTimer = null;
caster.OnSpellCast(m_Spell); spellCastCount.Add(1, new KeyValuePair<string, object>[]
caster.Region?.OnSpellCast(caster, m_Spell); {
new("SpellName", _spell.Name),
new("Skill", $"{_spell.CastSkill}"),
new("Map", caster.Map?.Name),
new("Region", caster.Region?.Name),
});
_spell._castTimer = null;
caster.OnSpellCast(_spell);
caster.Region?.OnSpellCast(caster, _spell);
caster.NextSpellTime = caster.NextSpellTime =
Core.TickCount + (int)m_Spell.GetCastRecovery().TotalMilliseconds; // Spell.NextSpellDelay; Core.TickCount + (int)_spell.GetCastRecovery().TotalMilliseconds; // Spell.NextSpellDelay;
caster.Delta(MobileDelta.Flags); // Update paralyze caster.Delta(MobileDelta.Flags); // Update paralyze
var originalTarget = caster.Target; var originalTarget = caster.Target;
m_Spell.OnCast(); _spell.OnCast();
if (caster.Player && caster.Target != originalTarget) if (caster.Player && caster.Target != originalTarget)
{ {
caster.Target?.BeginTimeout(caster, 30000); // 30 seconds caster.Target?.BeginTimeout(caster, 30000); // 30 seconds
} }
m_Spell._castTimer = null; _spell._castTimer = null;
} }
} }

View file

@ -44,6 +44,7 @@
<PackageReference Include="Argon2.Bindings" Version="1.16.1" /> <PackageReference Include="Argon2.Bindings" Version="1.16.1" />
<PackageReference Include="ModernUO.CodeGeneratedEvents.Annotations" Version="1.0.0" /> <PackageReference Include="ModernUO.CodeGeneratedEvents.Annotations" Version="1.0.0" />
<PackageReference Include="ModernUO.CodeGeneratedEvents.Generator" Version="1.0.3.2" PrivateAssets="all" /> <PackageReference Include="ModernUO.CodeGeneratedEvents.Generator" Version="1.0.3.2" PrivateAssets="all" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="9.0.0-rc.2.24473.5" />
<PackageReference Include="Zstd.Binaries" Version="1.6.0" /> <PackageReference Include="Zstd.Binaries" Version="1.6.0" />
<PackageReference Include="ModernUO.Serialization.Annotations" Version="2.9.1" /> <PackageReference Include="ModernUO.Serialization.Annotations" Version="2.9.1" />

33
docker-compose.yml Normal file
View file

@ -0,0 +1,33 @@
version: "2"
services:
# Collector
otel-collector:
image: otel/opentelemetry-collector:0.88.0
restart: always
command: ["--config=/etc/otel-collector-config.yaml", "${OTELCOL_ARGS}"]
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
ports:
- "8888:8888" # Prometheus metrics exposed by the collector
- "8889:8889" # Prometheus exporter metrics
- "13133:13133" # health_check extension
- "4317:4317" # OTLP gRPC receiver
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: always
volumes:
- ./prometheus.yaml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana
container_name: grafana
restart: unless-stopped
ports:
- '3000:3000'
volumes:
- ./grafana:/etc/grafana/provisioning/datasources
- ./grafana-storage:/var/lib/grafana

9
grafana/datasources.yml Normal file
View file

@ -0,0 +1,9 @@
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
url: http://prometheus:9090
isDefault: true
access: proxy
editable: true

View file

@ -0,0 +1,25 @@
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
exporters:
prometheus:
endpoint: "0.0.0.0:8889"
debug:
processors:
batch:
extensions:
health_check:
service:
extensions: [health_check]
pipelines:
metrics:
receivers: [otlp]
processors: [batch]
exporters: [debug, prometheus]

6
prometheus.yaml Normal file
View file

@ -0,0 +1,6 @@
scrape_configs:
- job_name: 'otel-collector'
scrape_interval: 10s
static_configs:
- targets: ['otel-collector:8889']
- targets: ['otel-collector:8888']