<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Mauro Dalfreddo &#187; Hack</title>
	<atom:link href="http://www.maurodalfreddo.it/archives/tag/hack/feed" rel="self" type="application/rss+xml" />
	<link>http://www.maurodalfreddo.it</link>
	<description>My work, my life... my Blog</description>
	<lastBuildDate>Thu, 24 Jun 2010 12:10:20 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.4</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Eseguire Stored Procedures in una funzione UDF (SQL Server)</title>
		<link>http://www.maurodalfreddo.it/archives/328/eseguire-stored-procedures-in-una-funzione-udf-sql-server</link>
		<comments>http://www.maurodalfreddo.it/archives/328/eseguire-stored-procedures-in-una-funzione-udf-sql-server#comments</comments>
		<pubDate>Wed, 28 Apr 2010 20:34:56 +0000</pubDate>
		<dc:creator>Mauro Dalfreddo</dc:creator>
				<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Software]]></category>
		<category><![CDATA[Hack]]></category>
		<category><![CDATA[Performance]]></category>

		<guid isPermaLink="false">http://www.maurodalfreddo.it/?p=328</guid>
		<description><![CDATA[E'possibile eseguire stored procedures in functions UDF con Microsoft SQL SERVER attraverso un workaround. Analisi della problematica e codifica di una .NET external function che permette l'esecuzione di dynamic SQL. (SQL Server: How to execute Stored procedures into a UDF function)]]></description>
			<content:encoded><![CDATA[<p><em>Un workaround per eseguire stored procedures in functions UDF con Microsoft SQL SERVER. Analizziamo la problematica e scriviamo una .NET external function che permette l&#8217;esecuzione di dynamic SQL. </em></p>
<p>Ultimamente mi sono scontrato con la problematica di eseguire una stored procedure da una funzione SQL UDF (<a href="http://www.google.it/search?hl=it&amp;rlz=1G1GGLQ_ITIT252&amp;q=How+to+execute+Stored+procedures+into+a+UDF+function&amp;meta=&amp;aq=f&amp;aqi=&amp;aql=&amp;oq=&amp;gs_rfai=" target="_blank">google</a>: <em>How to execute Stored procedures into a UDF function</em>).<br />
L&#8217;esecuzione di codice INSERT/UPDATE/DELETE/STORED PROCEDURE che alterano il contesto esterno alla funzione non è possibile <a href="http://msdn.microsoft.com/en-us/library/ms191320(SQL.90).aspx" target="_blank">by design</a>; è possibile solo apportare modifiche a variabili locali definite nella funzione. Infatti appena tentiamo di alterare il contesto del database otteniamo uno dei seguenti messaggi di errore, rispettivamente se stiamo utilizzando comandi TSQL INSERT/UPDATE/DELETE, chiamando STORED PROCEDURE o cercando di eseguire SQL dinamico:</p>
<p><span style="color: #ff0000;">Invalid use of side-effecting or time-dependent operator in &#8216;INSERT&#8217; within a function.</span></p>
<p><span style="color: #ff0000;">Only functions and extended stored procedures can be executed from within a function.</span></p>
<p><span style="color: #ff0000;">Invalid use of side-effecting or time-dependent operator in &#8216;EXECUTE STRING&#8217; within a function.</span></p>
<p>L&#8217;utilizzo delle funzioni SQL è utilizzato per leggere e applicare una certa logica ai dati letti, ma non a trasformarli o modificarli. E&#8217;difficile pensare che leggendo dei dati da una tabella con una SELECT questi vengano modificati sotto il naso.<br />
Se ci dovessero essere logiche più complesse, come l&#8217;utilizzo di tabelle di appoggio temporanee, certo è preferibile e doveroso racchiudere il codice in una stored procedure.</p>
<p>Questo in linea di principio e come best practice.<br />
Un caso che ho dovuto affrontare era quello dell&#8217;ottimizzazione delle performance di una serie di applicazioni. Queste, accedendo al DB, ed utilizzando gli strumenti offerti TSQL, utilizzavano tutte, in più punti e ripetutamente una certa funzione, che, accedendo a dati esterni al DB con protocollo TCP/IP, risultava particolarmente lenta a rispondere.</p>
<p>Da qui l&#8217;idea di implementare un <em><strong>meccanismo di caching</strong></em> nella funzione stessa; non era assolutamente pensabile cambiare la logica<br />
applicativa modificando il codice e le modalità di interfacciamento al DB. L&#8217;operazione doveva essere completamente trasparente per le applicazioni!</p>
<p>L&#8217;algoritmo di caching può essere banalmente il seguente:</p>
<pre>IF (prima volta)
 BEGIN
  recupera i dati online
  memorizzali nella cache
 ELSE
  recupera i dati dalla cache
 END</pre>
<p>Includere tale logica in una funzione SQL è semplice&#8230;<br />
Peccato che il significato di <em>memorizzare i dati</em> è quello di utilizzare un&#8217;istruzione del tipo INSERT/UPDATE/ST.PROCEDURE e che quindi modifica il contesto esterno alla funzione.</p>
<p>Nel seguito espongo la soluzione di <strong>come eseguire una stored procedure in una funzione</strong>, descrivendo i passi utili a creare una logica di caching sopra descritta.</p>
<p> </p>
<p><span id="more-328"></span></p>
<p>Immaginiamo di avere una funzionalità di basso livello (my_very_slow_function) che, dopo essersi connessa con un tal sistema autorizzativo, restituisca i ruoli associati ad un utente.<br />
Vogliamo utilizzare questa funzionalità in una funzione di più alto livello (my_function) che incapsuli la logica (quindi una funzione wrapper)</p>
<pre>CREATE FUNCTION [dbo].my_very_slow_function(@login nvarchar(50))
RETURNS TABLE
AS
RETURN
(  
 SELECT 'Administrator' as Role,0 as Type UNION
 SELECT 'Contributor', 0 UNION   
 SELECT 'Public role', 0 UNION   
 SELECT @login, 1
)</pre>
<pre>SELECT type,role FROM [dbo].my_very_slow_function('DOMAIN\testuser') ORDER BY type,role
<span style="color: #3366ff;"><em>type role</em>
0    Administrator
0    Contributor
0    Public role
1    DOMAIN\testuser</span></pre>
<pre>CREATE FUNCTION [dbo].[my_function](@login nvarchar(50))
RETURNS
@roles TABLE (role varchar(400), type int)
AS
BEGIN
 INSERT INTO @roles (role,type)
  SELECT role,type FROM my_very_slow_function(@login)
  RETURN
END</pre>
<pre>SELECT type,role FROM [dbo].my_function('DOMAIN\testuser') ORDER BY type,role</pre>
<p>Vogliamo quindi inserire nella funzione wrapper my_function la logica di caching. Creiamo la tabella per la cache e modifichiamo opportunamente la funzione.</p>
<pre>CREATE TABLE dbo.MYCACHE (
login VARCHAR(50) NOT NULL,
role VARCHAR(400) NOT NULL,
type INT NOT NULL,
ts datetime DEFAULT getdate(),
PRIMARY KEY (login,role)
)</pre>
<pre>GO</pre>
<pre>ALTER FUNCTION [dbo].[my_function](@login nvarchar(50))
RETURNS
@roles TABLE (role varchar(400), type int)
AS
BEGIN
 INSERT INTO @roles (role,type)
  SELECT role,type FROM dbo.MYCACHE
  WHERE login = @login
 IF @@ROWCOUNT = 0
 BEGIN  
  INSERT INTO [dbo].MYCACHE (login,role,type)
   SELECT @login,role,type FROM my_very_slow_function(@login)
  INSERT INTO @roles (role,type)
   SELECT role,type FROM dbo.MYCACHE
   WHERE login = @login
 END  
  RETURN
END</pre>
<p>Se tentiamo di modificare la funzione in tal senso otteniamo il seguente errore:</p>
<pre><span style="color: #ff0000;">Msg 443, Level 16, State 15, Procedure my_function, Line 11
Invalid use of side-effecting or time-dependent operator in 'INSERT' within a function.</span></pre>
<p>Cerchiamo di seguire un&#8217;altra strada: eseguire nella funzione una stored procedure contenente la logica del caching.</p>
<pre>CREATE PROCEDURE [dbo].MYCACHEINSERT 
 @login VARCHAR(50) 
AS
BEGIN
 INSERT INTO [dbo].MYCACHE (login,role,type)
  select @login,role,type
  FROM dbo.my_very_slow_function(@login) 
END
GO

ALTER FUNCTION [dbo].[my_function](@login nvarchar(50))
RETURNS
@roles TABLE (role varchar(400), type int)
AS
BEGIN
 DECLARE @sql NVARCHAR(400)

 INSERT INTO @roles (role,type)
  SELECT role,type FROM dbo.MYCACHE
  WHERE login = @login
 IF @@ROWCOUNT = 0
 BEGIN  
  exec dbo.MYCACHEINSERT @login
  
  INSERT INTO @roles (role,type)
   SELECT role,type FROM dbo.MYCACHE
   WHERE login = @login
 END  
  RETURN
END
GO</pre>
<p>Questa volta la creazione/aggiornamento non hanno problemi ma si presenta un errore in fase di esecuzione</p>
<pre>SELECT type,role FROM [dbo].my_function('DOMAIN\testuser') ORDER BY type,role</pre>
<pre><span style="color: #ff0000;">Msg 557, Level 16, State 2, Line 1
Only functions and extended stored procedures can be executed from within a function.
</span></pre>
<p><span style="color: #000000;">Possiamo anche provare a modificare la funzione per farle eseguire codice SQL dinamico, e questa volta otteniamo un errore in fase di modifica.</span></p>
<pre>ALTER FUNCTION [dbo].[my_function](@login nvarchar(50))
RETURNS
@roles TABLE (role varchar(400), type int)
AS
BEGIN
 DECLARE @sql NVARCHAR(400)

 INSERT INTO @roles (role,type)
  SELECT role,type FROM dbo.MYCACHE
  WHERE login = @login
 IF @@ROWCOUNT = 0
 BEGIN  
  SELECT @sql = 'exec dbo.MYCACHEINSERT '''+@login+''''
  exec(@sql)
  
  INSERT INTO @roles (role,type)
   SELECT role,type FROM dbo.MYCACHE
   WHERE login = @login
 END  
  RETURN
END</pre>
<pre><span style="color: #ff0000;">Msg 443, Level 16, State 14, Procedure my_function, Line 14
Invalid use of side-effecting or time-dependent operator in 'EXECUTE STRING' within a function.</span></pre>
<pre> </pre>
<p>La spiegazione nella difformità di comportamento dal precedente tentativo sta nel fatto che si può usare EXEC giusto per chiamare Extended Stored Procedures, ovvero le stored procedures scritte in C e compilate. Per definizione non si può cambiare lo stato del Database da dentro la funzione UDF, e così non si può costruire SQL dinamico ed eseguirlo con la stored procedure estesa &#8220;sp_executesql&#8221; (EXEC).</p>
<p>Documentandomi in Internet ho trovato un modo per eseguire uno statement SQL di aggiornamento/inserimento usando la funzione OPENQUERY e un LINKED SERVER che si autoreferenzia:</p>
<pre>SELECT count(*) FROM OPENQUERY(LINKED SERVER,'INSERT INTO ....')</pre>
<p>Peccato che la stringa di testo deve essere una costante, e non è possibile sostituirla con variabili e/o espressioni.</p>
<p>L&#8217;unico modo by design per risolvere il problema è quello di scrivere un&#8217;extended stored procedure in C, correndo il rischio che questa non funzioni più nelle prossime versioni di SQL Server, visto che la scrittura di questo tipo di procedures è deprecata. Scrivendo una external stored procedure in C# (.NET) non si risolve il problema, visto che non viene marcata come &#8220;extended&#8221; da SQL Server e che quindi non è possibile usarla dentro una funzione UDF.</p>
<p>Anche scrivendo una funzione external in C# ed eseguendo un&#8217;operazione INERT, ottenendo la connessione dal contesto con</p>
<pre><span style="color: #339966;">SqlConnection conn = new SqlConnection("context connection= true");</span></pre>
<p>otterremmo gli errori iniziali per i quali non è possibile modificare il contesto del DB. L&#8217;unico modo alla fine è quello di scrivere una funzione esterna C# ed istanziare una nuova connessione, ma sempre allo stesso database. In tal modo si inganna SQL Server, che non può più riconoscere l&#8217;eventuale modifica al contesto esterno.</p>
<p>Ora apriamo Visual Studio e creiamo un nuovo progetto SQL Server &#8220;MYUDF&#8221; in C#. Nelle proprietà del progetto ricodiamoci di impostare l&#8217;opzione &#8220;Sign the assembly&#8221;.</p>
<p>Poi aggiungiamo un nuovo file al progetto dti tipo Funzione UDF:</p>
<pre><span style="color: #339966;">using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

public partial class UserDefinedFunctions
{
    [Microsoft.SqlServer.Server.SqlFunction(Name = "ExecuteSQL", </span></pre>
<pre><span style="color: #339966;">     </span><span style="color: #339966;">DataAccess = DataAccessKind.Read, IsDeterministic = false)]</span></pre>
<pre><span style="color: #339966;">    public static int ExecuteSQL(SqlString sql, SqlString connstr)
    {
        using (SqlConnection conn = new SqlConnection(connstr.Value))
        {
            int res=0;
            try
            {                            
                conn.Open();               
                SqlCommand cmd = conn.CreateCommand();
                cmd.CommandText = sql.Value;               
                res = (int)cmd.ExecuteNonQuery();               
            }
            catch (Exception ex)
            {               
                throw;
            }
            finally {                               
                conn.Close();               
            }
            return res;
        }
       
    }
}</span></pre>
<p>La funzione riceve dall&#8217;input due parametri: la stringa contenente il codice SQL e quella per la connessione ad un DB. I controlli per la sicurezza e la gestione dell&#8217;errore è stata volutamente trascurata per semplificare la lettura. Al build del progetto verrà prodotto l&#8217;assembly MYUDF.dll, che dovrà essere copiato sul database server in una certa cartella, diciamo C:\TEMP. Creiamo e carichiamo l&#8217;assembly nel DB con il comando TSQL &#8220;CREATE ASSEMBLY&#8221;:</p>
<p>L&#8217;istruzione CREATE ASSEMBLY MYUDF può fallire e produrre i seguenti messaggi d&#8217;errore:</p>
<p>A) <span style="color: #ff0000;">Msg 10327, Level 14, State 1, Line 1 </span></p>
<p><span style="color: #ff0000;">CREATE ASSEMBLY for assembly &#8216;MYUDF&#8217; failed because assembly &#8216;MYUDF&#8217; is not authorized for PERMISSION_SET = UNSAFE.  The assembly is authorized when either of the following is true: the database owner (DBO) has UNSAFE ASSEMBLY permission and the database has the TRUSTWORTHY database property on; or the assembly is signed with a certificate or an asymmetric key that has a corresponding login with UNSAFE ASSEMBLY permission. </span></p>
<pre>ALTER DATABASE MYDB set trustworthy on;</pre>
<p>B) <span style="color: #ff0000;">Msg 0, Level 20, State 0, Line 0  </span></p>
<p><span style="color: #ff0000;">Errore grave durante l&#8217;esecuzione del comando corrente. Annullare i risultati eventuali.</span></p>
<p>Questo è un BUG noto (almeno per SQL2005), basta eseguire il seguente comando: <em>exec sp_changedbowner &#8216;&lt;username of the db attacher, ‘sa’, or any windows authenticated login&gt;&#8217;</em></p>
<pre>EXEC sp_changedbowner 'sa'

CREATE ASSEMBLY MYUDF
AUTHORIZATION [dbo] FROM 'C:\TEMP\MYUDF.dll' WITH PERMISSION_SET = UNSAFE;

CREATE FUNCTION [dbo].[ExecuteSQL](@sql [nvarchar](4000),@conn [nvarchar](200))
RETURNS int
AS EXTERNAL NAME MYUDF.[UserDefinedFunctions].ExecuteSQL</pre>
<pre>GRANT EXECUTE ON [dbo].[ExecuteSQL] TO [public]</pre>
<p>Abbiamo appena creato la funzione UDF .NET, ed ora ci prepariamo a testarla:</p>
<pre>select [dbo].[ExecuteSQL]('exec dbo.MYCACHEINSERT ''WORKGROUP\user1''','Data Source=MYSERVER;Initial Catalog=MYDB;Integrated Security=False;user id=dbu;password=dbu;')

<span style="color: #ff0000;">Msg 6522, Level 16, State 1, Line 1
A .NET Framework error occurred during execution of user defined routine or aggregate 'ExecuteSQL':
System.Data.SqlClient.SqlException: EXECUTE permission denied on object 'MYCACHEINSERT', database 'MYDB', schema 'dbo'.</span></pre>
<pre>GRANT EXECUTE ON [dbo].MYCACHEINSERT TO [dbu]</pre>
<p>Ora che abbiamo dato i permessi &#8220;rieseguiamo&#8221; la funzione, che &#8220;magicamente&#8221; ritorna i risultati attesi. Infatti nella tabella ci sono 4 righe. Bisogna far notare che è stato creata una login specifica &#8220;dbu&#8221; per inserire i dati nella tabella di cache e solo per quello scopo. Poi integriamo la chiamata ad ExecuteSQL in my_function.</p>
<pre>SELECT * FROM dbo.MYCACHE
<span style="color: #3366ff;"><em>login           role            type ts</em>
WORKGROUP\user1 Administrator   0    2010-04-25 12:08:13.513
WORKGROUP\user1 Contributor     0    2010-04-25 12:08:13.513
WORKGROUP\user1 Public role     0    2010-04-25 12:08:13.513
WORKGROUP\user1 WORKGROUP\user1 1    2010-04-25 12:08:13.513</span>
SELECT count(*) FROM dbo.MYCACHE
<span style="color: #3366ff;">4</span>

ALTER FUNCTION [dbo].[my_function](@login nvarchar(50))
RETURNS
@roles TABLE (role varchar(400), type int)
AS
BEGIN
 DECLARE @sql NVARCHAR(400)
 DECLARE @conn NVARCHAR(200)
 DECLARE @count INT

 INSERT INTO @roles (role,type)
  SELECT role,type FROM dbo.MYCACHE
  WHERE login = @login
 IF @@ROWCOUNT = 0
 BEGIN  
  SELECT @sql = 'exec dbo.MYCACHEINSERT '''+@login+'''',
   @conn = 'Data Source=MYSERVER;Initial Catalog=MYDB;Integrated Security=False;user id=dbu;password=dbu;'
  SELECT @count = [dbo].[ExecuteSQL] (@sql,@conn)
  
  INSERT INTO @roles (role,type)
   SELECT role,type FROM dbo.MYCACHE
   WHERE login = @login
 END  
  RETURN
END

SELECT type,role FROM [dbo].my_function('DOMAIN\testuser') ORDER BY type,role
<span style="color: #3366ff;"><em>type role</em>
0    Administrator
0    Contributor
0    Public role
1    DOMAIN\testuser</span>

SELECT count(*) FROM dbo.MYCACHE
<span style="color: #3366ff;">8</span></pre>
<p>Verifica effettuata e possiamo affermare che&#8230; il gioco è fatto! A questo punto abbiamo terminato di implementare il caching che ci prefiggevamo. Infatti le chiamate successive alla prima sono molto più veloci nell&#8217;esecuzione. Ora la funzione wrapper my_function può essere utilizzata in altri contesti, in altre funzioni o stored procedures.</p>
<p>Per esempio:</p>
<pre>CREATE FUNCTION [dbo].[my_function2](@login nvarchar(50),@param1 varchar(50))
RETURNS TABLE
AS
RETURN
( 
 SELECT count(*) as N, @param1 as M FROM [dbo].my_function(@login)  
)
SELECT * FROM [dbo].[my_function2]('DOMAIN\test2','param')
<span style="color: #3366ff;"><em>N M</em>
4 param</span></pre>
<p>Di seguito espongo una problematica che ho incontrato nell&#8217;utilizzare la tecnica sopra esposta in una stored procedure che utilizza un oggetto non locale quale una tabella temporanea (ricordo che è comunque una TABLE nel tempdb). Con simili oggetti si verrebbe a creare una transazione distribuita, ma SQL Server, capendo che la connessione istanziata dal codice .NET, individua un conflitto di sessioni. E da qui il messaggio di errore di cui sotto.</p>
<pre>CREATE PROCEDURE [dbo].[my_procedure]  
 @login varchar(50),
 @param1 varchar(50)
AS
BEGIN 
 SET NOCOUNT ON;
 SELECT count(*) as N, @param1 as M FROM [dbo].my_function(@login)  
END
exec [dbo].[my_procedure] 'DOMAIN\test2','param'
--N M
--4 param
ALTER PROCEDURE [dbo].[my_procedure]  
 @login varchar(50),
 @param1 varchar(50)
AS
BEGIN 
 SET NOCOUNT ON;
 SELECT count(*) as N, @param1 as M INTO #temp FROM [dbo].my_function(@login)  
 SELECT * FROm #temp
END
GO
exec [dbo].[my_procedure] 'DOMAIN\test5','param'</pre>
<pre><span style="color: #ff0000;">Msg 6522, Level 16, State 1, Procedure my_procedure, Line 7
A .NET Framework error occurred during execution of user defined routine or aggregate 'ExecuteSQL':
System.Data.SqlClient.SqlException: Transaction context in use by another session.</span></pre>
<p>La soluzione è più semplice del previsto: poichè SQL Server tenta implicitamente di costruire una transazione distribuita, il workaround è quello di inibire a priori tale possibilità attraverso il parametro opzionale <strong>Enlist</strong> nella connectionstring. E infatti, dopo la modifica della stringa di connessione, l&#8217;esecuzione funziona come ci si aspetta, ritornando i valori aspettati.</p>
<pre>ALTER FUNCTION [dbo].[my_function](@login nvarchar(50))
RETURNS
@roles TABLE (role varchar(400), type int)
AS
BEGIN
 DECLARE @sql NVARCHAR(400)
 DECLARE @conn NVARCHAR(200)
 DECLARE @count INT

 INSERT INTO @roles (role,type)
  SELECT role,type FROM dbo.MYCACHE
  WHERE login = @login
 IF @@ROWCOUNT = 0
 BEGIN  
  SELECT @sql = 'exec dbo.MYCACHEINSERT '''+@login+'''',
   @conn = 'Data Source=MYSERVER;Initial Catalog=MYDB;Integrated Security=False;user id=dbu;password=dbu;Enlist=no'
  SELECT @count = [dbo].[ExecuteSQL] (@sql,@conn)
  
  INSERT INTO @roles (role,type)
   SELECT role,type FROM dbo.MYCACHE
   WHERE login = @login
 END  
  RETURN
END

exec [dbo].[my_procedure] 'DOMAIN\test7','param'
<span style="color: #3366ff;"><em>N M</em>
4 param</span></pre>
<p><strong>Conclusioni</strong></p>
<p>L&#8217;utilizzo di stored procedures in funzioni è inibito in SQL Server by design, ma creando una external UDF in C#.NET è possibile aggirare l&#8217;ostacolo. E&#8217;stato presentato il codice e un esempio di come implementare il caching per una funzione &#8220;lenta&#8221;.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.maurodalfreddo.it/archives/328/eseguire-stored-procedures-in-una-funzione-udf-sql-server/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Web Application Stress Tool anche su XP</title>
		<link>http://www.maurodalfreddo.it/archives/249/web-application-stress-tool-anche-su-xp</link>
		<comments>http://www.maurodalfreddo.it/archives/249/web-application-stress-tool-anche-su-xp#comments</comments>
		<pubDate>Tue, 15 Sep 2009 09:34:11 +0000</pubDate>
		<dc:creator>Mauro Dalfreddo</dc:creator>
				<category><![CDATA[Software]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[Hack]]></category>
		<category><![CDATA[Performance]]></category>

		<guid isPermaLink="false">http://www.maurodalfreddo.it/?p=249</guid>
		<description><![CDATA[Hack: come attivare tutte le funzionalità di Microsoft Web Application Stress Tool su XP e Vista]]></description>
			<content:encoded><![CDATA[<p><em>Un hack : come attivare tutte le funzionalità di WAST su XP e Vista</em></p>
<p>Sviluppare ed implementare un sito o un&#8217;applicazione web è un processo creativo che può essere più o meno interessante e quindi più o meno divertente&#8230;</p>
<p>E quando finisce il divertimento cominciano i grattacapi! Sicuramente qualcuno nel management o il cliente che ha commissionato il progetto o forse noi stessi vorremmo conoscere quantitativamente le sue prestazioni, in termini di tempi di risposta in funzione del numero di utenti.</p>
<p>Infine potremo arrivare a determinare la capacità del sito, ovvero il numero massimo di richieste/sec aventi tempi di risposta decenti (ovvero prima il numero di utenti appena prima del degrado/collasso del sistema)</p>
<p><img class="aligncenter size-full wp-image-265" title="Load test" src="http://www.maurodalfreddo.it/wp-content/uploads/2009/09/grafico.jpg" alt="Load test" width="493" height="202" /></p>
<p>Quindi dopo aver sviluppato e pubblicato un sito o una web application conviene affrontare l&#8217;annoso problema dei test di carico e della simulazione realistica delle navigazioni degli utenti per:</p>
<ul>
<li>vedere  come si comporta il sistema</li>
<li> se sussitono colli di bottiglia, aree o sottosistemi dove è possibile ottimizzare qualcosa </li>
<li>dove è meglio iniziare ad effettuare il cosiddetto tuning.</li>
</ul>
<p>Allo scopo si possono trovare vari tools commerciali e opensource.<br />
E&#8217; possibile trovare una lista abbastanza esaustiva dei software all&#8217;url <a href="http://www.softwareqatest.com/qatweb1.html">http://www.softwareqatest.com/qatweb1.html</a>.</p>
<p>Nell&#8217;ultima esperienza personale, non avendo (mai) molto tempo a disposizione mi sono concentrato su alcuni software grafici e interfacciabili con i sistemi Windows Server:<br />
- l&#8217;ottimo ma assi costoso <a href="http://www.neotys.com" target="_blank">Neoload</a>, che permette di controllare e verificare graficamente  tutte le fasi del processo dal design alla fase di test e l&#8217;analisi dei risultati<br />
- gratuitamente in casa Microsoft si trovano WCAT (Web Capacity Analysis Tool), largamente programmabile ma solo tramite script,<br />
e WAST (<a href="http://www.iis.net/downloads/default.aspx?tabid=34&amp;g=6&amp;i=1298" target="_blank">Web Application Stress Tool &#8211; Homer </a>), il ben noto ma vetusto tool grafico.</p>
<p>WAST (o WAS) è uno strumento di simulazione e di carico sviluppato e rilasciato molti anni or sono da Microsoft, ma è un piccolo gioiellino, poichè permette di controllare e verificare graficamente tutte le fasi del processodi test:</p>
<ul>
<li>la registrazione visuale della navigazione effettuata con il browser</li>
<li>la creazione dei profili delle pagine di test</li>
<li>la definizione degli utenti</li>
<li>la distribuzione delle tipologie di test</li>
<li>la definizione del pool di macchine che partecipano al test di carico</li>
<li>la definizione dei contatori (performance counter) delle macchine oggetto di test</li>
<li>la gestione del test vero e proprio, ovvero lo stress del sito o dell&#8217;applicazione web</li>
<li>la collezione e l&#8217;elaborazione dei risultati, anche se in forma testuale.</li>
</ul>
<p>Purtroppo WAST è supportato ufficialmente solo su Microsoft Windows NT 4.0 SP 4+ e Microsoft Windows 2000, sistemi operativi che ormai non esistono (quasi) più negli ambiti di produzione.</p>
<p>L&#8217;installazione su Windows XP và praticamente sempre a buon fine, ed eseguendo WAST si possono sperimentare da subito le varie funzionalità tranne due:</p>
<ul>
<li>non funzionano performance counter</li>
<li>non funziona la comunicazione con i client del pool</li>
</ul>
<p>Su Windows Vista invece si ottiene da subito un errore (<a href=" http://weblogs.asp.net/steveschofield/archive/2007/03/10/iis7-post-32-web-application-stress-tool-on-vista.aspx" target="_blank">Steve Schofield Weblog &#8211; WAST on Vista </a>), ma basta caricare il file msvcp50.DLL in c:\windows\system32 (poiché non è compreso di default nel SO).</p>
<p>Vista l&#8217;indubbia utilità dell&#8217;applicazione, mi sono intestardito nel far funzionare WAST anche sui SO più recenti&#8230;e alla fine, dopo un po&#8217; di analisi e troubleshooting ci sono riuscito.</p>
<p><strong>Performance counters</strong></p>
<p>Per far funzionare la finestra di ricerca e di aggiunta dei performance counters delle macchine remote, nonchè il polling dei valori a runtime, basta copiare nella directory del programma il file <strong>pdh.dll</strong>, che si trova nella distribuzione di Windows 2000, e che è infatti la libreria che contiene le funzioni per i contatori.</p>
<p>Sicuramente WAS è stato compilato nel 2000 utilizzando quella versione di libreria (5.0.2174.1), mentre su XP c&#8217;e&#8217; quella nuova (5.1.2600.3536)</p>
<p style="text-align: center;"><a href="http://www.maurodalfreddo.it/wp-content/uploads/2009/09/wast_xp_perf_counters.jpg" target="_blank"><img class="aligncenter size-medium wp-image-258" title="Abilitare le funzionalità relative ai performance counter" src="http://www.maurodalfreddo.it/wp-content/uploads/2009/09/wast_xp_perf_counters-300x178.jpg" alt="Abilitare le funzionalità relative ai performance counter" width="300" height="178" /></a></p>
<p><strong>Clients</strong></p>
<p>Bisogna tenere presente che WAST è un tool client/server: l&#8217;interfaccia client (hclient.exe) colloquia localmente e/o remotamente (con tecnologia DCOM) con un windows service (webtool.exe); inoltre i dati sono resi persistenti su un database access.</p>
<p>Per far funzionare la comunicazione con i client definiti nel pool bisogna quindi  &#8220;giocare&#8221; con i pemessi DCOM (dcomcnfg.exe).</p>
<p>Si deve ricordare inoltre che, la comunicazione ed in generale i sistemi Microsoft prima del SP2 di Windows XP non erano securizzati di default, e quindi, per far funzionare il nostro WAST su XP, bisogna &#8220;rilassare&#8221; un pochino la sicurezza.</p>
<p style="text-align: center;"><a href="http://www.maurodalfreddo.it/wp-content/uploads/2009/09/wast_xp_clients.jpg" target="_blank"><img class="size-medium wp-image-257    aligncenter" title="Abilitare le funzionalità per il pool di client su WAST" src="http://www.maurodalfreddo.it/wp-content/uploads/2009/09/wast_xp_clients-300x207.jpg" alt="Abilitare le funzionalità per il pool di client su WAST" width="300" height="207" /></a></p>
<p><a href="http://www.maurodalfreddo.it/wp-content/uploads/2009/09/wast_xp_clients.jpg" target="_blank"></a></p>
<p style="text-align: left;"> Procedimento:</p>
<ol>
<li>Eseguire dcomcnfg.exe</li>
<li>Visualizzare le Proprietà di Servizi Componenti &#8211; Computer &#8211; Risorse Computer</li>
<li>Nel Tab Proprietà predefinite selezionare la casella Abilita servizi Internet COM in questo computer (che di default è deselezionato)</li>
<li>Nel Tab Protezione COM: Modificare i Limiti delle autorizzazioni di accesso abilitare per l&#8217;ACCESSO ANONIMO l&#8217;accesso remoto;<br />
Modificare i Limiti delle autorizzazioni di esecuzione e attivazione  abilitare per Everyone l&#8217;avvio remoto e l&#8217;attivazione remota</li>
<li>Entrare nel dettaglio dei componenti DCOM e aprire le Proprietà di &#8220;WebTool&#8221;: personalizzare eventualmente le ACL in modo tale che Everyone possa avviare ed attivare remotamente il componente</li>
</ol>
<p>E il gioco è fatto! Provare per credere&#8230;</p>
<p><strong>Risorse</strong></p>
<p> <a href="http://www.maurodalfreddo.it/wp-content/uploads/2009/09/WAST_XP.zip"><img style="border: 0px;" src="/img/zipico.jpg" border="0" alt="" height="32" /> DLL aggiuntive per WAST su XP</a></p>
<p><a href="http://www.maurodalfreddo.it/wp-content/uploads/2009/09/DCOM-Settings-for-WAST-on-XP.pdf"><img src="/img/pdfico.jpg" border="0" alt="" /> DCOM Settings for WAST on XP</a></p>
<p><a href="http://www.maurodalfreddo.it/wp-content/uploads/2009/09/lamincard_broli.jpg"></a></p>
<p><strong>Riferimenti</strong></p>
<p>- How To Install and Use the Web Application Stress (WAS) Tool</p>
<p><a href="http://support.microsoft.com/kb/313559/en-us">http://support.microsoft.com/kb/313559/en-us</a></p>
<p>- Introducing Microsoft Web Application Stress Tool (BY Jigesh Shah)</p>
<p><a href="http://www1bpt.bridgeport.edu/sed/projects/cs597/Fall_2002/jishah/web_application_stress.htm">http://www1bpt.bridgeport.edu/sed/projects/cs597/Fall_2002/jishah/web_application_stress.htm</a></p>
<p>- Load Testing Web Applications using Microsoft&#8217;s Web Application Stress Tool (By Rick Strahl)</p>
<p><a href="http://www.code-magazine.com/article.aspx?quickid=0001091&amp;page=1">http://www.code-magazine.com/article.aspx?quickid=0001091&amp;page=1</a></p>
<p>- Download Microsoft Web Application Stress Tool &#8211; Homer:</p>
<p><a href="http://www.iis.net/downloads/default.aspx?tabid=34&amp;g=6&amp;i=1298">http://www.iis.net/downloads/default.aspx?tabid=34&amp;g=6&amp;i=1298</a></p>
<p>- Steve Schofield Weblog &#8211; WAST on Vista</p>
<p><a href="http://weblogs.asp.net/steveschofield/archive/2007/03/10/iis7-post-32-web-application-stress-tool-on-vista.aspx">http://weblogs.asp.net/steveschofield/archive/2007/03/10/iis7-post-32-web-application-stress-tool-on-vista.aspx</a></p>
<p>- Recentemente c&#8217;e&#8217; stato qualche problema a reperire WAST sul sito Microsoft download, per cui allego il <a href="http://www.maurodalfreddo.it/wp-content/uploads/2009/09/web_stress_tool_setup.zip">setup</a>, almeno finchè rimarrano tali problemi.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.maurodalfreddo.it/archives/249/web-application-stress-tool-anche-su-xp/feed</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
	</channel>
</rss>
