This is something I see in almost all clients when we perform a PFE Health Check. The customer will have lots of data being inserted into the OpsDB from agents, about monitors that are constantly changing state. This can have a very negative effect on overall performance of the database – because it can be a lot of data, and the RMS is busy handling the state calculation, and synching this data about the state and any alert changes to the warehouse.
Many times the OpsMgr admin has no idea this is happening, because the alerts appear, and then auto-resolve so fast, you never see them – or don’t see them long enough to detect there is a problem. I have seen databases where the statechangeevent table was the largest in the database – caused by these issues.
Too many state changes are generally caused by one or both, of two issues:
1. Badly written monitors that flip flop constantly. Normally – this happens when you target a multi-instance perf counter incorrectly. See my POST on this topic for more information.
2. HealthService restarts. See my POST on this topic.
How can I detect if this is happening in my environment?
That is the right question! For now – you can run a handful of SQL queries, which will show you the most common state changes going on in your environments. These are listed on my SQL query blog page in the State section:
Noisiest monitors in the database: (Note – these will include old state changes – might not be current)
select distinct top 50 count(sce.StateId) as NumStateChanges, m.MonitorName, mt.typename AS TargetClass
from StateChangeEvent sce with (nolock)
join state s with (nolock) on sce.StateId = s.StateId
join monitor m with (nolock) on s.MonitorId = m.MonitorId
join managedtype mt with (nolock) on m.TargetManagedEntityType = mt.ManagedTypeId
where m.IsUnitMonitor = 1
group by m.MonitorName,mt.typename
order by NumStateChanges desc
The above query will show us which monitors are flipping the most in the entire database. This includes recent, and OLD data. You have to be careful looking at this output – as you might spent a lot of time focusing on a monitor that had a problem long ago. You see – we will only groom out old state changes for monitors that are CURRENTLY in a HEALTHY state, AT THE TIME that grooming runs. We will not groom old state change events if the monitor is Disabled (unmonitored), in Maintenance Mode, Warning State, or Critical State.
What?
This means that if you had a major issue with a monitor in the past, and you solved it by disabling the monitor, we will NEVER, EVER groom that junk out. This doesn't really pose a problem, it just leaves a little database bloat, and messy statechangeevent views in HealthExplorer. But the real issue for me is – it makes it a bit tougher to only look at the problem monitors NOW.
To see if you have really old state change data leftover in your database, you can run the following query:
SELECT DATEDIFF(d, MIN(TimeAdded), GETDATE()) AS [Current] FROM statechangeevent
You might find you have a couple YEARS worth of old state data.
So – I have taken the built in grooming stored procedure, and modified the statement to groom out ALL statechange data, and only keep the number of days you have set in the UI. (The default setting is 7 days). I like to run this “cleanup” script from time to time, to clear out the old data, and whenever I am troubleshooting current issues with monitor flip-flop. Here is the SQL query statement:
To clean up old StateChangeEvent data for state changes that are older than the defined grooming period, such as monitors currently in a disabled, warning, or critical state. By default we only groom monitor statechangeevents where the monitor is enabled and healthy at the time of grooming.
USE [OperationsManager]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
BEGINSET NOCOUNT ON
DECLARE @Err int
DECLARE @Ret int
DECLARE @DaysToKeep tinyint
DECLARE @GroomingThresholdLocal datetime
DECLARE @GroomingThresholdUTC datetime
DECLARE @TimeGroomingRan datetime
DECLARE @MaxTimeGroomed datetime
DECLARE @RowCount int
SET @TimeGroomingRan = getutcdate()SELECT @GroomingThresholdLocal = dbo.fn_GroomingThreshold(DaysToKeep, getdate())
FROM dbo.PartitionAndGroomingSettings
WHERE ObjectName = 'StateChangeEvent'EXEC dbo.p_ConvertLocalTimeToUTC @GroomingThresholdLocal, @GroomingThresholdUTC OUT
SET @Err = @@ERRORIF (@Err <> 0)
BEGIN
GOTO Error_Exit
ENDSET @RowCount = 1
-- This is to update the settings table
-- with the max groomed data
SELECT @MaxTimeGroomed = MAX(TimeGenerated)
FROM dbo.StateChangeEvent
WHERE TimeGenerated < @GroomingThresholdUTCIF @MaxTimeGroomed IS NULL
GOTO Success_Exit-- Instead of the FK DELETE CASCADE handling the deletion of the rows from
-- the MJS table, do it explicitly. Performance is much better this way.
DELETE MJS
FROM dbo.MonitoringJobStatus MJS
JOIN dbo.StateChangeEvent SCE
ON SCE.StateChangeEventId = MJS.StateChangeEventId
JOIN dbo.State S WITH(NOLOCK)
ON SCE.[StateId] = S.[StateId]
WHERE SCE.TimeGenerated < @GroomingThresholdUTC
AND S.[HealthState] in (0,1,2,3)SELECT @Err = @@ERROR
IF (@Err <> 0)
BEGIN
GOTO Error_Exit
ENDWHILE (@RowCount > 0)
BEGIN
-- Delete StateChangeEvents that are older than @GroomingThresholdUTC
-- We are doing this in chunks in separate transactions on
-- purpose: to avoid the transaction log to grow too large.
DELETE TOP (10000) SCE
FROM dbo.StateChangeEvent SCE
JOIN dbo.State S WITH(NOLOCK)
ON SCE.[StateId] = S.[StateId]
WHERE TimeGenerated < @GroomingThresholdUTC
AND S.[HealthState] in (0,1,2,3)SELECT @Err = @@ERROR, @RowCount = @@ROWCOUNT
IF (@Err <> 0)
BEGIN
GOTO Error_Exit
END
ENDUPDATE dbo.PartitionAndGroomingSettings
SET GroomingRunTime = @TimeGroomingRan,
DataGroomedMaxTime = @MaxTimeGroomed
WHERE ObjectName = 'StateChangeEvent'SELECT @Err = @@ERROR, @RowCount = @@ROWCOUNT
IF (@Err <> 0)
BEGIN
GOTO Error_Exit
END
Success_Exit:
Error_Exit:
END
Once this is cleaned up – you can re-run the DATEDIFF query – and see you should only have the same number of days as set in your UI retention setting for database grooming.
Now – you can run the “Most common state changes” query – and identify which monitors are causing the problem.
Look for monitors at the top with MUCH higher numbers than all others. This will be “monitor flip flop” and you should use Health Explorer to find that monitor on a few instances – and figure out why it is changing state so much in the past few days. Common conditions for this one are badly written monitors that target a single instance object, but monitor a multi-instance perf counter. You can read more on that HERE. Also – just poor overall tuning can cause this – or poorly written custom script based monitors.
If you see a LOT of similar monitors at the top, with very similar state change counts, this is often indicative of HealthService restarts. The Health service will submit new state change data every time it starts up. So if the agent is bouncing every 10 minutes, that is a new state change for ALL monitors on that agent, every 10 minutes. You can read more about this condition at THIS blog post.