Automatisation du serveur Jenkins SQL: renvoyer magnifiquement le résultat

Poursuivant le thème de l'organisation de Zero Touch PROD sous RDS . Les futurs administrateurs de base de données ne pourront pas se connecter directement aux serveurs PROD, mais pourront utiliser les travaux Jenkins pour un ensemble limité d'opérations. DBA démarre le travail et après un certain temps reçoit une lettre avec un rapport sur la fin de cette opération. Examinons les moyens de présenter ces résultats à l'utilisateur.



Texte brut


Commençons par le plus trivial. La première méthode est si simple qu'il n'y a rien à dire en général (l'auteur utilise ci-après les travaux FreeStyle):



sqlcmd fait quelque chose et nous le présentons à l'utilisateur. Idéal pour, par exemple, les travaux de sauvegarde:



Soit dit en passant, nous n'oublions pas que sous RDS, la sauvegarde / restauration est asynchrone, nous devons donc l'attendre:

declare @rds table (id int, task_type varchar(128), database_name sysname, pct int, duration int, lifecycle varchar(128), taskinfo varchar(max) null, upd datetime, cre datetime, s3 varchar(256), ovr int, KMS varchar(256) null) waitfor delay '00:00:20' insert into @rds exec msdb.dbo.rds_task_status @db_name='{db}' select @xid=max(id) from @rds again: waitfor delay '00:00:02' delete from @rds insert into @rds exec msdb.dbo.rds_task_status @db_name='{db}' # {db} substituted with db name by powershell select @stat=lifecycle,@info=taskinfo from @rds where id=@xid if @stat not in ('ERROR','SUCCESS','CANCELLED') goto again 

La deuxième façon, CSV


Ici aussi, tout est très simple:



Cependant, cette méthode ne fonctionne que si les données renvoyées au CSV sont «simples». Si vous essayez de renvoyer, par exemple, la liste des requêtes gourmandes en ressources CPU TOP N, le CSV «se corrodera» car le texte de la requête peut contenir n'importe quel caractère - virgules, guillemets et même sauts de ligne. Par conséquent, nous avons besoin de quelque chose de plus compliqué.

Belles balises HTML


Je vais vous donner un extrait de code tout de suite

 $Header = @" <style> TABLE {border-width: 1px; border-style: solid; border-color: black; border-collapse: collapse;} TH {border-width: 1px; padding: 3px; border-style: solid; border-color: black; background-color: #6495ED;} TD {border-width: 1px; padding: 3px; border-style: solid; border-color: black;} </style> "@ $Result = invoke-Sqlcmd -ConnectionString $jstr -Query "select * from DbInv" ` | Select-Object -Property * -ExcludeProperty "ItemArray", "RowError", "RowState", "Table", "HasErrors" if ($Result -eq $null) { $cnt = 0; } elseif ($Result.getType().FullName -eq "System.Management.Automation.PSCustomObject") { $cnt = 1; } else { $cnt = $Result.Rows.Count; } if ($cnt -gt 0) { $body = "<h2>My table</h2>" $Result | ConvertTo-HTML -Title "Rows" -Head $header -body $body ` | Out-File "res.log" -Append -Encoding UTF8 } else { "<h3>No data</h3>" | Out-File "res.log" -Append -Encoding UTF8 } 

Au fait, faites attention à la ligne avec System.Management.Automation.PSCustomObject, c'est magique, s'il y a exactement une ligne dans la grille, il y a eu quelques problèmes. La décision a été prise à partir d'Internet sans grande compréhension. En conséquence, vous obtenez la sortie, écrite quelque chose comme ceci:



Dessiner des graphiques


Attention: code perverti ci-dessous!

Il y a une requête amusante sur le serveur SQL qui affiche le CPU dans les N dernières minutes - il s'avère que le camarade Major se souvient de tout! Essayez celui-ci:

 DECLARE @ts_now bigint = (SELECT cpu_ticks/(cpu_ticks/ms_ticks) FROM sys.dm_os_sys_info WITH (NOLOCK)); SELECT TOP(256) DATEADD(ms, -1 * (@ts_now - [timestamp]), GETDATE()) AS [EventTime], SQLProcessUtilization AS [SQLCPU], 100 - SystemIdle - SQLProcessUtilization AS [OtherCPU] FROM (SELECT record.value('(./Record/@id)[1]', 'int') AS record_id, record.value('(./Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS [SystemIdle], record.value('(./Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS [SQLProcessUtilization], [timestamp] FROM (SELECT [timestamp], CONVERT(xml, record) AS [record] FROM sys.dm_os_ring_buffers WITH (NOLOCK) WHERE ring_buffer_type = N'RING_BUFFER_SCHEDULER_MONITOR' AND record LIKE N'%<SystemHealth>%') AS x) AS y ORDER BY 1 DESC OPTION (RECOMPILE); 

Maintenant, en utilisant cette mise en forme (variable $ Fragment)
 <table style="width: 100%"><tbody><tr style="background-color: white; height: 2pt;"> <td style="width: SQLCPU%; background-color: green;"></td> <td style="width: OtherCPU%; background-color: blue;"></td> <td style="width: REST%; background-color: #C0C0C0;"></td></tr></tbody> </table> 

Nous pouvons former le corps de la lettre:
 $Result = invoke-Sqlcmd -ConnectionString $connstr -Query $Query ` | Select-Object -Property * -ExcludeProperty ` "ItemArray", "RowError", "RowState", "Table", "HasErrors" if ($Result.HasRows) { foreach($item in $Result) { $time = $itemEventTime $sqlcpu = $item.SQLCPU $other = $itemOtherCPU $rest = 100 - $sqlcpu - $other $f = $fragment -replace "SQLCPU", $sqlcpu $f = $f -replace "OtherCPU", $other $f = $f -replace "REST", $rest $f | Out-File "res.log" -Append -Encoding UTF8 } 

Qui ressemblerait à ceci:



Oui, Monsieur en sait beaucoup sur les perversions! Fait intéressant, ce code contient: Powershell (écrit dessus), SQL, Xquery, HTML. C'est dommage que nous ne puissions pas ajouter Javascript au HTML (car c'est pour l'écriture), mais c'est le devoir de tout le monde de moudre du code Python (qui peut être utilisé en SQL)!

Sortie de trace du profileur SQL


Il est clair que la trace ne rentrera pas dans le CSV en raison du champ TextData. Mais il est également étrange d'afficher la grille dans une lettre - à la fois en raison de la taille et parce que souvent ces données sont utilisées pour une analyse plus approfondie. Par conséquent, nous faisons ce qui suit: nous invoquons via invoke-SqlCmd un certain script, dans les entrailles duquel cela se fait

 select SPID,EventClass,TextData, Duration,Reads,Writes,CPU, StartTime,EndTime,DatabaseName,HostName, ApplicationName,LoginName from ::fn_trace_gettable ( @filename , default ) 

De plus, sur un autre serveur accessible par DBA, il y a une base Traces avec un espace vide, une plaque modèle, prête à accepter toutes les colonnes spécifiées. Nous copions ce modèle dans une nouvelle table avec un nom unique:

 $dt = Get-Date -format "yyyyMMdd" $tm = Get-Date -format "hhmmss" $tableName = $srv + "_" + $dt + "_" + $tm $copytab = "select * into " + $tableName + " from Model" invoke-SqlCmd -ConnectionString $tstr -Query $copytab 

Et maintenant, nous pouvons y écrire notre trace en utilisant Data.SqlClient.SqlBulkCopy - J'ai déjà donné un exemple de cela ci-dessus. Oui, ce serait bien de masquer les constantes dans TextData:

 # mask data foreach ($Row in $Result) { $v = $Row["TextData"] $v = $v -replace "'([^']{2,})'", "'str'" -replace "[0-9][0-9]+", '999' $Row["TextData"] = $v } 

Nous remplaçons les nombres par plusieurs caractères d'une longueur de 999, et nous remplaçons les chaînes plus longues qu'un caractère par «str». Les nombres de 0 à 9 sont souvent utilisés comme drapeaux, et nous ne les touchons pas, ainsi que les chaînes vides et à caractère unique - parmi elles se trouvent souvent `` Y '', `` N '', etc.

Ajoutez des couleurs à nos vies (strictement 18+)


Dans les comprimés, vous souhaitez souvent mettre en évidence les cellules qui nécessitent une attention. Par exemple, FAILS, un niveau élevé de fragmentation, etc. Bien sûr, cela peut également être fait sur du SQL nu, en formant du HTML à l'aide de PRINT, et dans Jenkins, vous pouvez définir le type de fichier HTML:

 declare @body varchar(max), @chunk varchar(max) set @body='<font face="Lucida Console" size="3">' set @body=@body+'<b>Server name: '+@@servername+'</b><br>' set @body=@body+'<br><br>' set @body=@body+'<table><tr><th>Job</th><th>Last Run</th><th>Avg Duration, sec</th><th>Last Run, Sec</th><th>Last Status</th></tr>' print @body DECLARE tab CURSOR FOR SELECT '<tr><td>'+name+'</td><td>'+ LastRun+'</td><td>'+ convert(varchar,AvgDuration)+'</td><td>'+ convert(varchar,LastDuration)+'</td><td>'+ case when LastStatus<>'Succeeded' then '<font color="red">' else '' end+ LastStatus+ case when LastStatus<>'Succeeded' then '</font>' else '' end+ +'</td><td>' from #j2 OPEN tab; FETCH NEXT FROM tab into @chunk WHILE @@FETCH_STATUS = 0 BEGIN print @chunk FETCH NEXT FROM tab into @chunk; END CLOSE tab; DEALLOCATE tab; print '</table>' 

Pourquoi ai-je écrit un tel code?



Mais il existe une solution plus belle. ConvertTo-HTML ne nous permet pas de coloriser les cellules, mais nous pouvons le faire après coup. Par exemple, nous voulons sélectionner des cellules avec un niveau de fragmentation supérieur à 80 et supérieur à 90. Ajoutez des styles:

 <style> .SQLmarkup-red { color: red; background-color: yellow; } .SQLmarkup-yellow { color: black; background-color: #FFFFE0; } .SQLmarkup-default { color: black; background-color: white; } </style> 

Dans le qi lui-même, nous ajouterons une colonne factice directement à la colonne que nous voulons coloriser. La colonne doit s'appeler SQLmarkup - quelque chose:
 case when ps.avg_fragmentation_in_percent>=90.0 then 'SQLmarkup-red' when ps.avg_fragmentation_in_percent>=80.0 then 'SQLmarkup-yellow' else 'SQLmarkup-default' end as [SQLmarkup-1], ps.avg_fragmentation_in_percent, 

Maintenant, après avoir reçu le code HTML créé par Powershell, nous allons supprimer la colonne factice de l'en-tête et transférer la valeur de la colonne vers le style dans le corps de données. Cela se fait en seulement deux permutations:

 $html = $html ` -replace "<th>SQLmarkup[^<]*</th>", "" ` -replace "<td>SQLmarkup-(.+?)</td><td>",'<td class="SQLmarkup-$1">' 

Résultat:



N'est-ce pas élégant? Bien que non, quelque chose que cette coloration me rappelle

Source: https://habr.com/ru/post/fr447242/


All Articles