@@ -196,6 +196,94 @@ IF DB_ID('PerformanceMonitor') IS NOT NULL
196196 Logger . Info ( $ "Dropped PerformanceMonitor database and Agent jobs on '{ server . DisplayName } '") ;
197197 }
198198
199+ public class PurgeResult
200+ {
201+ public int RowsDeleted { get ; set ; }
202+ public int TableCount { get ; set ; }
203+ public int DurationMs { get ; set ; }
204+ public string Status { get ; set ; } = "" ;
205+ public string ? Message { get ; set ; }
206+ }
207+
208+ /// <summary>
209+ /// Runs config.data_retention against the PerformanceMonitor database on the given server.
210+ /// </summary>
211+ /// <param name="retentionDaysOverride">
212+ /// null = use per-collector retention from config.collection_schedule.
213+ /// 0 = TRUNCATE every collect.* table.
214+ /// N > 0 = override every table's cutoff to N days.
215+ /// </param>
216+ public async Task < PurgeResult > RunDataRetentionAsync (
217+ ServerConnection server ,
218+ int ? retentionDaysOverride )
219+ {
220+ var connectionString = server . GetConnectionString ( _credentialService ) ;
221+ var builder = new SqlConnectionStringBuilder ( connectionString )
222+ {
223+ InitialCatalog = "PerformanceMonitor" ,
224+ ConnectTimeout = 10
225+ } ;
226+
227+ using var connection = new SqlConnection ( builder . ConnectionString ) ;
228+ await connection . OpenAsync ( ) ;
229+
230+ using ( var cmd = new SqlCommand ( "config.data_retention" , connection ) )
231+ {
232+ cmd . CommandType = System . Data . CommandType . StoredProcedure ;
233+ cmd . CommandTimeout = 600 ;
234+
235+ if ( retentionDaysOverride . HasValue )
236+ {
237+ cmd . Parameters . Add ( new SqlParameter ( "@retention_days" , System . Data . SqlDbType . Int ) { Value = retentionDaysOverride . Value } ) ;
238+ }
239+
240+ await cmd . ExecuteNonQueryAsync ( ) ;
241+ }
242+
243+ using var readCmd = new SqlCommand ( @"
244+ SELECT TOP (1)
245+ cl.collection_status,
246+ cl.rows_collected,
247+ cl.duration_ms,
248+ cl.error_message
249+ FROM config.collection_log AS cl
250+ WHERE cl.collector_name = N'data_retention'
251+ ORDER BY cl.collection_time DESC;" , connection ) ;
252+ readCmd . CommandTimeout = 30 ;
253+
254+ using var reader = await readCmd . ExecuteReaderAsync ( ) ;
255+ var result = new PurgeResult ( ) ;
256+
257+ if ( await reader . ReadAsync ( ) )
258+ {
259+ result . Status = reader . IsDBNull ( 0 ) ? "" : reader . GetString ( 0 ) ;
260+ result . RowsDeleted = reader . IsDBNull ( 1 ) ? 0 : reader . GetInt32 ( 1 ) ;
261+ result . DurationMs = reader . IsDBNull ( 2 ) ? 0 : reader . GetInt32 ( 2 ) ;
262+ result . Message = reader . IsDBNull ( 3 ) ? null : reader . GetString ( 3 ) ;
263+
264+ if ( result . Message is not null && result . Message . StartsWith ( "Cleaned " , StringComparison . Ordinal ) )
265+ {
266+ int spaceIdx = result . Message . IndexOf ( ' ' , 8 ) ;
267+ if ( spaceIdx > 8 && int . TryParse ( result . Message . AsSpan ( 8 , spaceIdx - 8 ) , out int tableCount ) )
268+ {
269+ result . TableCount = tableCount ;
270+ }
271+ }
272+ else if ( result . Message is not null && result . Message . StartsWith ( "TRUNCATE all: " , StringComparison . Ordinal ) )
273+ {
274+ int spaceIdx = result . Message . IndexOf ( ' ' , 14 ) ;
275+ if ( spaceIdx > 14 && int . TryParse ( result . Message . AsSpan ( 14 , spaceIdx - 14 ) , out int tableCount ) )
276+ {
277+ result . TableCount = tableCount ;
278+ }
279+ }
280+ }
281+
282+ Logger . Info ( $ "Ran data_retention on '{ server . DisplayName } ': status={ result . Status } , rowsDeleted={ result . RowsDeleted } , tables={ result . TableCount } , durationMs={ result . DurationMs } ") ;
283+
284+ return result ;
285+ }
286+
199287 public void UpdateLastConnected ( string id )
200288 {
201289 lock ( _serversLock )
0 commit comments