Analysis Services: automatic server-side trace

Il modo migliore per monitorare nel tempo l’utilizzo dei nostri database è utilizzare il SQL Server Profiler e registrare gli eventi che riteniamo migliori per la nostra analisi.

Per avere il minimo impatto sui sistemi è sempre consigliabile utilizzare una trace server-side, e non un’applicazione, con tanto di interfaccia grafica, su una macchina client.

 

Con il motore relazionale realizzare quanto sopra è molto semplice e veloce.

Si utilizzano stored procedure di sistema in grado di eseguire l’analisi server-side:

  • sp_trace_create
  • sp_trace_setevent
  • sp_trace_setfilter
  • sp_trace_setstatus

E, una volta ottenuta la trace, possiamo facilmente leggerla (ne parlavo già qui) con la funzione fn_trace_gettable, in un modo simile a:

 SELECT *
FROM ::fn_trace_gettable('C:\data\myTrace.trc', default) 

In rete si trovano diversi articoli su come impostare e schedulare una trace server-side, ad esempio qui.

 

Con il motore multidimensonale le cose si complicano un pò, poichè:

  1. non possiamo utilizzare stored procedure per impostare e gestire trace server-side
  2. una volta ottenuta la traccia non possiamo utilizzare la funzione fn_trace_gettable, come per il motore relazionale (la funzione non supporta il formato di trace registrato dagli Analysis Services), o qualsiasi altra funzione.

In sostanza, anche avendo “in mano” una traccia, non abbiamo nessun meccanismo che ci permetta di caricare immediatamente i suoi dati in una tabella su cui poter fare analisi.

 

Vediamo come possiamo risolvere il problema iniziando con l’utilizzare due query XMLA (XML for Analysis Reference, vedi qui) per:

  1. inizializzare e mandare in esecuzione la traccia
  2. stoppare la traccia

 

Per costruirla e mandarla in esecuzione è sufficiente costruire il nostro XMLA come spiegato qui.

Se, ad esempio, volessi registrare solo eventi di query end la mia query sarebbe:

 <Batch xmlns="https://schemas.microsoft.com/analysisservices/2003/engine"
xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/">
  <Create xmlns="https://schemas.microsoft.com/analysisservices/2003/engine">
    <ObjectDefinition>
      <Trace>
        <ID>DBA Performance Trace</ID>
        <Name>DBA Performance Trace</Name>

        <!-- Set your path -->
        <LogFileName>C:\data\myTrace.trc</LogFileName>

        <LogFileAppend>1</LogFileAppend>
        <AutoRestart>0</AutoRestart>

        <!--Logfilesize is in MB 5120 MB = 5 GB-->
        <LogFileSize>5120</LogFileSize>
        <LogFileRollover>0</LogFileRollover>

        <!-- Uncomment and update stoptime if you want to auto stop -->
        <!--StopTime>...</StopTime> -->

        <Events>
          <Event>
            <EventID>10</EventID>
            <Columns>
              <ColumnID>1</ColumnID>
              <ColumnID>2</ColumnID>
              <ColumnID>3</ColumnID>
              <ColumnID>5</ColumnID>
              <ColumnID>6</ColumnID>
              <ColumnID>24</ColumnID>
              <ColumnID>25</ColumnID>
              <ColumnID>28</ColumnID>
              <ColumnID>32</ColumnID>
              <ColumnID>33</ColumnID>
              <ColumnID>36</ColumnID>
              <ColumnID>37</ColumnID>
              <ColumnID>41</ColumnID>
              <ColumnID>42</ColumnID>
            </Columns>
          </Event>
        </Events>
        <Filter>
          <NotLike>
            <ColumnID>37</ColumnID>
            <Value>SQL Server Profiler</Value>
          </NotLike>
        </Filter>
      </Trace>
    </ObjectDefinition>
  </Create>
</Batch>

 

Per stoppare la traccia, sempre utilizzando XMLA, possiamo scrivere una query come:

 <Delete xmlns="https://schemas.microsoft.com/analysisservices/2003/engine" xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/">
  <Object>
    <TraceID>DBA Performance Trace</TraceID>
  </Object>
</Delete>

 

Resta un ultimo punto: come possiamo importare il contenuto registrato in una tabella relazionale per poterne fare un’analisi?

 

In maniera manuale è semplice: apriamo la traccia con SQL Server Profiler e memorizziamo il contenuto in una tabella.

Ma poichè vogliamo realizzare una soluzione automatica, la cosa è leggermente più complessa.

 

L’inserimento su tabella, degli eventi registrati su file, la possiamo realizzare tramite un’applicazione console .Net, costruita ad-hoc, in grado di leggere il file registrato e di scriverne i dati all’interno di una tabella SQL.

L’applicazione, per funzionare, ha la necessità di avere referenziati gli assembly:

  • Microsoft.SqlServer.ConnectionInfo
  • Microsoft.SqlServer.ConnectionInfoExtended

 

image

Gli assembly andranno selezionati sulla base della versione SQL che vogliamo tracciare.

Per SQL Server 2012: C:\Program Files (x86)\Microsoft SQL Server\110\SDK\Assemblies

 

Possiamo associare all’applicazione un suo file di configurazione per poter specificare:

  • Nome dell’istanza SQL Server dove andare a scrivere le righe lette dal file di traccia
  • Nome del database all’interno del quale generare la tabella
  • Nome della tabella dove inserire il contenuto della traccia
  • Percorso fisico del file di traccia da andare a leggere

Ad esempio:

 <appSettings>
  <add key="instance" value=".\sql2012"/>
  <add key="db" value="ASTrace"/>
  <add key="table" value="OutputTraceTable"/>
  <add key="pathTrc" value="C:\data\myTrace.trc"/>
</appSettings>

 

Questo una prima stesura dell’applicazione console:

 using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Trace;
using System.Configuration;

namespace ASTraceSQLTableLoad
{
    class Program
    {
        static void Main()
        {
            var instance = ConfigurationSettings.AppSettings["instance"];
            var db = ConfigurationSettings.AppSettings["db"];
            var table = ConfigurationSettings.AppSettings["table"];
            var pathTrc = ConfigurationSettings.AppSettings["pathTrc"];

            var connInfo = new SqlConnectionInfo(instance) { DatabaseName = db };
            var reader = new TraceFile();
            var writer = new TraceTable();

            try
            {
                reader.InitializeAsReader(pathTrc);
                writer.InitializeAsWriter(reader, connInfo, table);

                while (writer.Write()) { };

                writer.Close();
                reader.Close();
            }
            catch { }
        }
    }
}

Attenzione: l’applicazione deve essere compilata in modalità x86 poichè la libreria necessaria è solo a 32-bit.

image

Questo il messaggio di errore che si avrebbe con compilazioni x64:

The log provider for SQL Server Profiler was unable to load pfclnt.dll because it is only supported on 32-bit systems.

La traccia, con questa applicazione, viene caricata all’interno della tabella [OutputTraceTable], come fosse una tabella di staging.

Questa tabella viene costruita, in maniera automatica, dal metodo InitializeAsWriter chiamato nel codice.

 

Se volessi automatizzare tutto il processo potrei, tramite SSIS, costruire un package in grado di fare tutti i passaggi appena descritti e di aggiungere, dopo il caricamento della tabella di staging, un’azione in grado di portare i dati appena scritti in una seconda tabella che ci permetta di storicizzare le registrazioni fatte.

Questa tabella può essere costruita come:

 CREATE TABLE dbo.ASTrace
( 
    RowNumber int IDENTITY( 0 , 1 ) NOT NULL , 
    EventClass int NULL , 
    EventSubclass int NULL , 
    CurrentTime datetime NULL , 
    StartTime datetime NULL , 
    Duration bigint NULL , 
    CPUTime bigint NULL , 
    Error int NULL , 
    ConnectionID int NULL , 
    DatabaseName nvarchar( 128 )NULL , 
    NTUserName nvarchar( 128 )NULL , 
    NTDomainName nvarchar( 128 )NULL , 
    ClientProcessID int NULL , 
    ApplicationName nvarchar( 128 )NULL , 
    SPID int NULL , 
    TextData ntext NULL , 
    BinaryData image NULL , 
    PRIMARY KEY CLUSTERED( RowNumber ASC )
)

Quindi eseguire una INSERT come:

 INSERT INTO dbo.ASTrace
SELECT EventClass , 
       EventSubclass , 
       CurrentTime , 
       StartTime , 
       Duration , 
       CPUTime , 
       Error , 
       ConnectionID , 
       DatabaseName , 
       NTUserName , 
       NTDomainName , 
       ClientProcessID , 
       ApplicationName , 
       SPID , 
       TextData , 
       BinaryData
  FROM dbo.OutputTraceTable;

 

A questo punto, sulla tabella [ASTrace], abbiamo i nostri eventi registrati:

image

 

Se volessimo costruire il package SSIS, in grado di gestire tutte le azioni descritte poco sopra, potremmo avere qualcosa come:

image

Il package in questione, così definito, ci permette di:

  • Creare, se già non esistente, la tabella relazionale [ASTrace]  dove memorizzare e raccogliere nel tempo i dati presenti sulla traccia
  • Cancellare, se presente, la tabella [OutputTraceTable] generata automaticamente dalle funzioni di sistema chiamate da un’attività successiva
  • Fermare la traccia che si preoccupa di registrare e scrivere su file gli eventi di interesse
  • Inserire, all’interno della tabella [OutputTraceTable], i dati presenti sul file di traccia appena stoppato
  • Inserire le righe dalla tabella di stagin alla tabella [ASTrace]
  • Cancellare il file di traccia

 

In ottica di gestire in maniera schedulata sia il processo di start, che il processo di recupero e storicizzazione della traccia, possiamo costruire due job tramite SQL Agent:

  1. Job di start: tramite uno step di tipo “SQL Server Analysis Services Command” mandare in esecuzione la prima query XMLA
  2. Job di recupero: tramite uno step di tipo “SQL Server Integration Service Command” mandare in esecuzione il package appena descritto

Enjoy.