Jenkins SQL Server-Automatisierung: Das Ergebnis wird wunderschön zurückgegeben

Fortsetzung des Themas der Anordnung von Zero Touch PROD unter RDS . Zukünftige Datenbankadministratoren können keine direkte Verbindung zu PROD-Servern herstellen, können jedoch Jenkins- Jobs für eine begrenzte Anzahl von Vorgängen verwenden. Der DBA beginnt seine Arbeit und erhält nach einer Weile einen Brief mit einem Bericht über den Abschluss dieses Vorgangs. Schauen wir uns an, wie Sie diese Ergebnisse dem Benutzer präsentieren können.



Klartext


Beginnen wir mit dem Trivialsten. Die erste Methode ist so einfach, dass es im Allgemeinen nichts zu besprechen gibt (der Autor verwendet im Folgenden FreeStyle-Jobs):



sqlcmd macht etwas und wir präsentieren es dem Benutzer. Ideal zum Beispiel für Sicherungsjobs:



Übrigens vergessen wir nicht, dass das Sichern / Wiederherstellen unter RDS asynchron ist, also sollten wir darauf warten:

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 

Der zweite Weg, CSV


Alles hier ist auch sehr einfach:



Diese Methode funktioniert jedoch nur, wenn die an die CSV zurückgegebenen Daten „einfach“ sind. Wenn Sie beispielsweise versuchen, die Liste der TOP N CPU-intensiven Abfragen zurückzugeben, wird die CSV "korrodieren", da der Abfragetext beliebige Zeichen enthalten kann - Kommas, Anführungszeichen und sogar Zeilenumbrüche. Deshalb brauchen wir etwas Komplizierteres.

Schöne HTML-Tags


Ich gebe dir einen Code-Ausschnitt

 $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 } 

Achten Sie übrigens auf die Zeile mit System.Management.Automation.PSCustomObject. Es ist magisch, wenn genau eine Zeile im Raster vorhanden ist, gab es einige Probleme. Die Entscheidung wurde aus dem Internet ohne viel Verständnis getroffen. Als Ergebnis erhalten Sie die Ausgabe, die ungefähr so ​​geschrieben ist:



Zeichnen Sie Grafiken


Warnung: perverser Code unten!

Es gibt eine lustige Abfrage an den SQL Server, die die CPU in den letzten N Minuten anzeigt - es stellt sich heraus, dass Genosse Major sich an alles erinnert! Versuchen Sie dieses:

 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); 

Verwenden Sie jetzt diese Formatierung ($ Fragment Variable)
 <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> 

Wir können den Körper des Briefes bilden:
 $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 } 

Welches würde so aussehen:



Ja, Monsieur weiß viel über Perversionen! Interessanterweise enthält dieser Code: Powershell (darauf geschrieben), SQL, Xquery, HTML. Es ist schade, dass wir kein Javascript zu HTML hinzufügen können (da dies zum Schreiben dient), aber es ist jedermanns Pflicht, Python-Code (der in SQL verwendet werden kann) zu mahlen!

SQL-Profiler-Trace-Ausgabe


Es ist klar, dass die Ablaufverfolgung aufgrund des TextData-Felds nicht in die CSV „passt“. Es ist aber auch seltsam, das Raster in einem Buchstaben anzuzeigen - sowohl aufgrund der Größe als auch, weil diese Daten häufig für die weitere Analyse verwendet werden. Deshalb machen wir folgendes: Wir rufen durch invoke-SqlCmd ein bestimmtes Skript auf, in dessen Darm es ausgeführt wird

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

Auf einem anderen Server, auf den DBA zugreifen kann, befindet sich eine Traces-Basis mit einem leeren Leerzeichen, einer Modellplatte, die bereit ist, alle angegebenen Spalten zu akzeptieren. Wir kopieren dieses Modell in eine neue Tabelle mit einem eindeutigen Namen:

 $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 

Und jetzt können wir unseren Trace mit Data.SqlClient.SqlBulkCopy darauf schreiben - ich habe oben bereits ein Beispiel dafür gegeben. Ja, es wäre schön, Konstanten in TextData zu maskieren:

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

Wir ersetzen Zahlen durch mehr als ein Zeichen mit einer Länge von 999 und wir ersetzen Zeichenfolgen, die länger als ein Zeichen sind, durch 'str'. Zahlen von 0 bis 9 werden häufig als Flags verwendet, und wir berühren sie nicht sowie leere und einstellige Zeichenfolgen - darunter finden sich häufig 'Y', 'N' usw.

Fügen Sie unserem Leben Farben hinzu (streng 18+)


In den Tablets möchten Sie häufig die Zellen hervorheben, die Aufmerksamkeit erfordern. Zum Beispiel FAILS, ein hohes Maß an Fragmentierung usw. Dies kann natürlich auch in Bare SQL erfolgen, indem HTML mit PRINT erstellt wird, und in Jenkins können Sie den HTML-Dateityp festlegen:

 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>' 

Warum habe ich solchen Code geschrieben?



Aber es gibt eine schönere Lösung. Mit ConvertTo-HTML können wir die Zellen nicht einfärben , aber wir können es nachträglich tun. Zum Beispiel möchten wir Zellen mit einer Fragmentierungsstufe von mehr als 80 und mehr als 90 auswählen. Stile hinzufügen:

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

Im Qi selbst fügen wir der Spalte, die wir einfärben möchten, eine Dummy-Spalte direkt hinzu . Die Spalte sollte SQLmarkup heißen - etwas:
 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, 

Nachdem wir den von Powershell erstellten HTML-Code erhalten haben, entfernen wir die Dummy-Spalte aus dem Header und übertragen den Wert aus der Spalte in den Stil im Datenkörper. Dies geschieht in nur zwei Permutationen:

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

Ergebnis:



Ist es nicht elegant? Obwohl nein, erinnert mich diese Färbung an etwas

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


All Articles