Skip to content

Instantly share code, notes, and snippets.

@odinserj
Last active June 25, 2024 11:00
Show Gist options
  • Save odinserj/975b05848c728df3daf79015c542e8b6 to your computer and use it in GitHub Desktop.
Save odinserj/975b05848c728df3daf79015c542e8b6 to your computer and use it in GitHub Desktop.
// Copyright © 2020 Sergey Odinokov
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the “Software”), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// =====================================================================================================
//
// USAGE:
// 1. Register HangfireCountersFilter via GlobalJobFilters.Filters.Add(new HangfireCountersFilter());
// 2. Call dotnet counters monitor -p <PID> Hangfire.Core
//
// =====================================================================================================
using System;
using System.Diagnostics.Tracing;
using System.Threading;
using Hangfire.Client;
using Hangfire.Server;
using Hangfire.States;
using Hangfire.Storage;
namespace NetCoreSample
{
[EventSource(Name = HangfireCountersEventSource.SourceName)]
public class HangfireCountersEventSource : EventSource
{
// this name will be used as "provider" name with dotnet-counters
// ex: dotnet-counters monitor -p <pid> Hangfire.Core
private const string SourceName = "Hangfire.Core";
private long _created;
private long _performed;
private long _performedWithExceptions;
private long _succeeded;
private long _deleted;
private long _failed;
public HangfireCountersEventSource() : base(SourceName, EventSourceSettings.EtwSelfDescribingEventFormat)
{
CreateCounters();
}
public IncrementingPollingCounter CreatedPerSec { get; private set; }
public PollingCounter Created { get; private set; }
public IncrementingPollingCounter PerformedPerSec { get; private set; }
public PollingCounter Performed { get; private set; }
public IncrementingPollingCounter PerformedWithExceptionsPerSec { get; private set; }
public PollingCounter PerformedWithExceptions { get; private set; }
public IncrementingPollingCounter SucceededPerSec { get; private set; }
public PollingCounter Succeeded { get; private set; }
public IncrementingPollingCounter DeletedPerSec { get; private set; }
public PollingCounter Deleted { get; private set; }
public IncrementingPollingCounter FailedPerSec { get; private set; }
public PollingCounter Failed { get; private set; }
public void IncrementCreated()
{
Interlocked.Increment(ref _created);
}
public void IncrementPerformed()
{
Interlocked.Increment(ref _performed);
}
public void IncrementPerformedWithExceptions()
{
Interlocked.Increment(ref _performedWithExceptions);
}
public void IncrementSucceeded()
{
Interlocked.Increment(ref _succeeded);
}
public void IncrementDeleted()
{
Interlocked.Increment(ref _deleted);
}
public void IncrementFailed()
{
Interlocked.Increment(ref _failed);
}
private void CreateCounters()
{
CreatedPerSec = new IncrementingPollingCounter("created-rate", this, () => _created)
{
DisplayName = "Background Jobs Created",
DisplayRateTimeScale = new TimeSpan(0, 0, 1)
};
Created = new PollingCounter("created", this, () => _created)
{
DisplayName = "Background Jobs Created"
};
PerformedPerSec = new IncrementingPollingCounter("performed-rate", this, () => _performed)
{
DisplayName = "Background Jobs Performed",
DisplayRateTimeScale = new TimeSpan(0, 0, 1)
};
Performed = new PollingCounter("performed", this, () => _performed)
{
DisplayName = "Background Jobs Performed"
};
PerformedWithExceptionsPerSec = new IncrementingPollingCounter("performed-exceptions-rate", this, () => _performedWithExceptions)
{
DisplayName = "Background Jobs Performed with Exceptions",
DisplayRateTimeScale = new TimeSpan(0, 0, 1)
};
PerformedWithExceptions = new PollingCounter("performed-exceptions", this, () => _performedWithExceptions)
{
DisplayName = "Background Jobs Performed with Exceptions"
};
SucceededPerSec = new IncrementingPollingCounter("succeeded-rate", this, () => _succeeded)
{
DisplayName = "Transitions to Succeeded State",
DisplayRateTimeScale = new TimeSpan(0, 0, 1)
};
Succeeded = new PollingCounter("succeeded", this, () => _succeeded)
{
DisplayName = "Transitions to Succeeded State"
};
DeletedPerSec = new IncrementingPollingCounter("deleted-rate", this, () => _deleted)
{
DisplayName = "Transitions to Deleted State",
DisplayRateTimeScale = new TimeSpan(0, 0, 1)
};
Deleted = new PollingCounter("deleted", this, () => _deleted)
{
DisplayName = "Transitions to Deleted State"
};
FailedPerSec = new IncrementingPollingCounter("failed-rate", this, () => _failed)
{
DisplayName = "Transitions to Failed State",
DisplayRateTimeScale = new TimeSpan(0, 0, 1)
};
Failed = new PollingCounter("failed", this, () => _failed)
{
DisplayName = "Transitions to Failed State"
};
}
}
public class HangfireCountersFilter : IApplyStateFilter, IClientFilter, IServerFilter
{
private readonly HangfireCountersEventSource _counters;
public HangfireCountersFilter()
{
_counters = new HangfireCountersEventSource();
}
public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
if (context.NewState is FailedState)
{
_counters.IncrementFailed();
}
if (context.NewState is SucceededState)
{
_counters.IncrementSucceeded();
}
if (context.NewState is DeletedState)
{
_counters.IncrementDeleted();
}
}
public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
}
public void OnCreating(CreatingContext filterContext)
{
}
public void OnCreated(CreatedContext filterContext)
{
_counters.IncrementCreated();
}
public void OnPerforming(PerformingContext filterContext)
{
}
public void OnPerformed(PerformedContext filterContext)
{
_counters.IncrementPerformed();
if (filterContext.Exception != null && !filterContext.ExceptionHandled)
{
_counters.IncrementPerformedWithExceptions();
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment