28 de agosto de 2012

Avaliando a performance de queries no SQL Server

Muito da aceitação de um programa de computador por seus usuários pode ser atribuído à praticidade de sua interface gráfica e até mesmo a aparência que ela tenha. No entanto, a aceitação rapidamente se reverte se o programa apresentar problemas de performance. Com bastante frequência, a degradação de performance num programa está associado a queries mal projetadas submetidas a um banco de dados pelo programa.

Quando digo "mal projetada", eu me refiro a certas características das queries adotadas pelo programa, tais como não utilizar os índices apropriados das tabelas ou manter tabelas sem índice, fazer ligações ineficientes com outras tabelas, trazer registros demais que não serão utilizados ou ainda executar uma mesma query mais vezes do que o necessário.

A partir da sua versão 2005, o SQL Server mantém em tabelas virtuais uma série de informações a respeito das queries que foram executadas pelo banco de dados. Essas informações ficam armazenadas em memória e são descartadas quando o serviço do banco é desligado. No entanto, são informações bastante úteis para auxiliar na localização de problemas de performance gerados pelas queries mal planejadas.

Um usuário que queira consultar as informações disponibilizadas nessas tabelas virutais precisa ter atribuida a permissão VIEW SERVER STATE. Isso pode ser feito visualmente, através das propriedades do servidor no Management Studio ou via comando SQL, utilizando-se o GRANT.

Uma das views mais interessantes nesse contexto é a sys.dm_exec_query_stats, responsável pelas estatísticas de execução de queries. Ela traz, dentre outras informações, a quantidade de vezes que uma query foi executada, o tempo gasto nessas execuções, quantas linhas foram retornadas, a quantidade de leituras e gravações feitas durante a execução. Ela dá acesso também ao texto da query e ao plano de execução montado pelo SQL. O exemplo no quadro abaixo lista em ordem decrescente as queries que mais consumiram CPU:
SELECT qs.total_worker_time / qs.execution_count As 'Uso Médio da CPU',
qs.total_worker_time As 'Uso Total da CPU',
qs.execution_count As 'Qtde de Execuções',
qt.text As 'Query',
DB_NAME(qt.dbid) As 'Nome do Banco'
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) As qt
ORDER BY 1 DESC

Veja que a quantidade de vezes que uma query executou não é determinante para que ela tenha consumido mais CPU na média. Consulta semelhante pode ser montada para obter as queries que mais foram executadas (pra avaliar se todas as execuções são mesmo necessárias) ou a quantidade de linhas retornada (pra avaliar se não é possível restringir mais os registros, acrescentando outras comparações à cláusula WHERE). Os tempos listados estão em microsegundos; para convertê-lo em segundos, divida o valor por 1 milhão. É possível ainda levantar a quantidade de gravações feitas por uma query e avaliar se tantas alterações são mesmo necessárias

Outro detalhe a ser destacado no quadro anterior é que o texto da query não está na mesma view. Para obtê-lo, é necessário pegar o código contido no campo sql_handle e repassá-lo à função sys.dm_exec_sql_text.

Outra view bastante útil é a sys.dm_db_missing_index_details. Como o próprio nome diz, ela armazena detalhes sobre o uso de queries cujas execuções não estão otimizadas por que não foi possível encontrar na tabela um índice apropriado. Baseado nas informações retornadas por ela, podemos introduzir novos índices e melhorar a performance das consultas envolvendo as tabelas em questão. O quadro abaixo mostra um exemplo de consulta a essa view, listando as colunas encontradas na cláusula WHERE da query com problema, a tabela envolvida e a quantidade de vezes que a query problemática foi disparada:
SELECT equality_columns, inequality_columns, statement, count(*) As 'Qtde Exec'
FROM sys.dm_db_missing_index_details
WHERE DB_NAME(database_id) = 'ABC71DS94'
GROUP BY equality_columns, inequality_columns, statement
ORDER BY statement

A coluna statement traz o nome da tabela que está sem índice apropriado. A equality_columns lista as colunas usadas em comparações de igualdade no WHERE - algo como NOME_DO_CAMPO = VALOR. Em inequality_columns aparecem as colunas usadas em outros tipos de comparação do WHERE. Veja que é possível filtrar as informações por banco de dados instalado, permitindo avaliar os índices para cada banco isoladamente.

A lista de informações que podemos obter no DMV (Dynamic Management Views) inclui ainda os motivos de espera para uma query ser executada, dados de espelhamento e replicação, tamanho do banco de dados e suas partições, etc. A lista completa das informações disponíveis por esse meio pode ser encontrada em Dynamic Management Views and Functions no MSDN online.

1 de agosto de 2012

Aplicações em Java ainda valem a pena ?

Depois de protagonizar o primeiro grande ataque a sistemas Mac OS no início deste ano, quando uma vulnerabilidade crítica permitiu a infecção de mais de 600 mil Macs, o Java volta a ficar na berlinda. Desta vez, um pesquisador do Centro de Proteção a Malwares da Microsoft publicou um artigo intitulado "Como se proteger de malwares baseados em Java" onde aconselha os usuários a trabalharem sempre com as releases mais recentes da Virtual Machine e, quando for possível, que a deixem desativada para proteger seus sistemas. No caso de não utilizar aplicações Java, a recomendação é de remover completamente o JRE.

Na mesma linha, só que um pouco mais radical, Woody Leonhard defendeu em artigo publicado na InfoWorld.com que a melhor opção é mesmo abandonar a tecnologia, substituindo-a o mais rápido possível. O texto abaixo é uma tradução do artigo onde ele expressa sua opinião:
Quatro meses atrás, eu critiquei o Java -- ou, mais apropriadamente, o uso do Java Runtime Environment (JRE) -- no post "É hora de expulsar o Java da cidade". Quatro meses depois, a situação não melhorou um milímetro. De fato, ela piorou, se é que piorar é uma opção para o vetor de infecção número 1 em PCs e Macs.

Na semana passada, o pesquisador Matt Oh do Centro de Proteção Contra Malware da Microsoft publicou um artigo no TechNet sobre como se proteger contra os malwares baseados em Java. Para enfatizar o ponto, ele deu uma palestra no Black Hat 2012 no mesmo dia, dizendo que a situação com o Java está se deteriorando -- e não somente no Windows.

"Estamos vendo cada vez mais vulnerabilidades no Java sendo exploradas ... e uma vulnerabilidade no Java pode às vezes ser explorada em múltiplas plataformas," disse o pesquisador.

A principal preocupação para Oh são as brechas no sandbox. Se autores de malware conseguirem acesso fora dos limites do sandbox de execução do Java/JRE, eles podem tomar o controle de um sistema, não importando se ele está em Windows, Mac OS X ou Unix. Uma única vulnerabilidade no Java -- como a brecha de segurança "confusão de tipos" CVE-2012-1723, descoberta há apenas algumas semanas, ou a velha CVE-2012-0507, que levou à rede de robôs Flashback, com mais de 600 mil Macs infectados neste ano -- pode resultar numa exploração bem sucedida que burle as defesas do sistema operacional simplesmente por ele estar executando o Java.

"A confusão de tipos é uma vulnerabilidade que ocorre quando a verificação de segurança de tipos feita pelo JRE falha quando são fornecidos tipos diferentes do esperado para uma instrução. Alguns dos tipos do Java, como o ClassLoader, podem ser alvos desse ataque. Se a segurança de tipos dessas classes é quebrada, pode-se ter acesso a alguns métodos que não deveriam estar acessíveis a processos externos à própria classe. Esta violação na segurança de tipos leva, em última análise, ao comprometimento do sandbox do Java," disse Oh.

Para piorar, o fato do programa ser escrito em Java torna mais fácil ocultar a violação usando ferramentas disponíveis e bem documentadas da própria linguagem para embaralhar o código.

A recomendação de Oh é que você esteja sempre com o JRE mais atualizado e que o desabilite sempre que isso for possível. Se você não usa aplicações Java, então simplesmente desinstale o JRE.

Minha recomendação para TI é um pouco mais proativa: é hora de tirar seus usuários da esteira do JRE/JVM. Se você tem um produto que requer o JRE, migre-o. Se seus planos de negócio incluem aplicações Java, altere seus planos. Se você ou sua equipe de desenvolvimento programam aplicações client Java, é hora de diversificar suas habilidades.

Ao continuar usando Java, você está colocando sua empresa e seus clientes em risco.

Embora eu entenda as razões dele e concorde com sua argumentação - que é irretocável, considerando-se a agudez da crise atual - acredito que ainda seja cedo pra tomar uma decisão tal radical quanto simplesmente abdicar das aplicações feitas em Java. Digo isso porque me parece que a Oracle vem mantendo um plano razoavelmente consistente de atualizações da linguagem. Veja, por exemplo, o projeto JigSaw e os planos para levar Java para o iOS.

Apesar de às vezes a Oracle pecar na velocidade das correções, creio que o ideal é seguir as recomendações de Matt Oh: manter sempre atualizado o JRE e eventualmente desligá-lo (se possível) quando brechas críticas forem detectadas e a correção demorar a sair.

18 de julho de 2012

Assinando documentos XML com CAPICOM e Delphi

A Microsoft disponibiliza para o Windows uma biblioteca com tecnologia COM para tratar a criação e manipulação de arquivos XML. A biblioteca, chamada MSXML, está atualmente na versão 6 e suporta também transformações XSLT e validação através de esquemas XSD. Em apenas uma das versões (o MSXML5), foram incluídos ainda recursos para fazer assinatura digital de XMLs.

A versão 5 foi distribuída exclusivamente com o Microsoft Office para que os desenvolvedores dessa plataforma pudessem assinar XMLs. No entanto, uma busca na internet revela que a biblioteca é amplamente utilizada fora desse contexto.

Isto é uma boa notícia para quem usa Delphi (ou outra linguagem que suporte COM) pois o MSXML5, em associação com o CAPICOM, facilita bastante a tarefa de assinar XMLs. Neste post eu mostro como realizar este processo, considerando que você já tem montado o XML que quer assinar. O quadro abaixo mostra parte do XML de uma Nota Fiscal que vou usar como exemplo:
<?xml version="1.0" encoding="UTF-8" ?>
<enviNFe versao="2.00" xmlns="http://www.portalfiscal.inf.br/nfe">
<idLote>71</idLote>
<NFe>
<infNFe Id="NFe3508059978" versao="2.00">
<cUF>35</cUF>
<cNF>518005127</cNF>
<natOp>Venda a vista</natOp>
<mod>55</mod>
<serie>1</serie>
<dEmi>2012-05-06</dEmi>
<tpAmb>2</tpAmb>
</infNFe>
</NFe>
</enviNFe>

A primeira providência é importar os fontes dos ActiveX MSXML5 e CAPICOM para podermos utilizá-los no projeto Delphi. Há um resumo de como fazer essa importação neste endereço; para o MSXML5, a descrição da Type Library é Microsoft XML, v5.0. Ambos os fontes gerados devem ser incluídos na cláusula uses da unit que for fazer a assinatura.

Obviamente, vamos precisar de um certificado digital para realizar a assinatura. Utilizando o CAPICOM, podemos acessar o Certificate Store do Windows e localizar um apropriado, que tenha sido gerado para sua empresa. No entanto, as interfaces disponíveis no MSXML5 exigem que você informe um Certificate Store com o certificado que será utilizado, bem como sua cadeia de validação, se for necessário. Este passo extra é mostrado no quadro abaixo.
var store : IStore3;
cert : TCertificate;
lKey : IPrivateKey;
begin
cert := getCert;
lKey := Cert.PrivateKey;

{ Monta um Store em memória com o Certificado obtido antes }
store := CoStore.Create;
store.Open(CAPICOM_CURRENT_USER_STORE, 'My', CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED);
store.Add(cert.DefaultInterface);
{ ... }

A função GetCert utilizada acima é minha e segue as instruções descritas no post sobre acesso ao Certificate Store com CAPICOM. De resto, o código apenas cria um Store em memória e adiciona a ele o certificado encontrado. O trecho também salva a chave privada deste certificado, informação que será usada mais adiante para gerar a assinatura em si.

A seguir, precisamos preparar o XML do exemplo para receber a assinatura. É que a interface do MSXML5 responsável por essa etapa se baseia num nó Signature do XML. Esse nó descreve as transformações pelas quais o XML deve ser submetido antes de ser de fato assinado. O nó Signature deve ser criado como mostrado abaixo:
<?xml version="1.0" encoding="UTF-8" ?>
<enviNFe versao="2.00" xmlns="http://www.portalfiscal.inf.br/nfe">
<idLote>71</idLote>
<NFe>
{ ... }
<Signature>
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<Reference URI="#NFe3508059978">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue></DigestValue>
</Reference>
</SignedInfo>
<SignatureValue></SignatureValue>
</Signature>
</NFe>
</enviNFe>

Como podemos ver acima, o nó também deixa reservado um local para receber o hash (DigestValue) e outro para a própria assinatura (SignatureValue), que serão preenchidos automaticamente. A única parte variável do bloco preparado acima é o atributo URI da tag Reference, valor que reflete a identificação da Nota Fiscal contida no XML. Ou seja, deve-se montar o valor desse atributo a partir do valor do atributo Id da tag infNFe, precedendo este valor com o sinal #. Como o próprio nome diz, essa parte do XML faz referência ao nó (e seus filhos) que será resguardado pela assinatura digital.

Uma vez que o XML está preparado, podemos partir para a assinatura propriamente dita, o que é conseguido através da interface IXMLDigitalSignature do MSXML5. Então, precisamos criar uma instância dela e lhe fornecedor os dados para a assinatura:
var {...}
xmlDoc: IXmlDomDocument3;
noSignature : IXMLDOMElement;
xmlSign : IXMLDigitalSignature;
xmlDsigKey: IXMLDSigKey;
begin
{...}
{ Monta o XML e prepara a tag Signature }
xmlDoc := PreparaXML ();
noSignature := EncontraNodeSignature (xmlDoc);

if (noSignature <> Nil) then begin
xmlSign := CoMXDigitalSignature50.Create;
xmlSign.signature := noSignature;
xmlSign.store := store;

{ Monta a chave com o tipo exigido pelo método Sign }
xmlDsigKey := xmlSign.createKeyFromCSP(lKey.ProviderType, lKey.ProviderName, lKey.ContainerName, 0);
{ Assina o XML }
xmlSign.sign(xmlDsigKey, NOKEYINFO);
{...}
end;
end;

Como podemos ver no trecho de código acima, o IXMLDigitalSignature precisa que indiquemos qual é o nó Signature do nosso XmlDomDocument e o Certificate Store que construímos no início do post. Agora, temos que chamar o método sign para completar o serviço. Para funcionar, ele precisa de algumas informações baseadas na chave privada do certificado. No exemplo acima, utilizei a chave recuperada a partir do certificado para criar uma instância da interface IXMLDSigKey contendo as informações necessárias.

O segundo parâmetro da função sign determina como os dados da chave serão incluídos na estrutura do XML. No exemplo, usei o valor NOKEYINFO para que tais dados não sejam incluídos automaticamente.

Após chamar a função sign, as tags que deixamos reservadas no nó Signature são automaticamente calculadas e preenchidas. Finalizando, para que o XML fique no padrão exigido para Notas Fiscais pela Receita Federal, só falta incluir um nó com os dados do certificado usado para assinar. Mostrei como fazer isso no post Certificado Digital para inclusão no XML com CAPICOM e Delphi.

22 de junho de 2012

Trabalhando com informações sobres tipos no Delphi em tempo de execução - parte II

A extensão do mecanismo de RTTI (Run Time Type Information) introduzida no Delphi 2010 - e que eu citei no meu último post - abre novas possibilidades de soluções para arquitetura de softwares feitos em Delphi. Isso é particularmente útil para desenvolvedores de infraestrutura de software, situação onde é comum a necessidade de se criar rotinas genéricas para certas tarefas.

Customizar classes estendendo o RTTI também permite melhorar a organização da solução já que estas classes serão mais coesas. Isto é, funcionalidades que não são centrais podem ser transferidas para outras classes associadas. É o caso, por exemplo, da exportação de objetos para XML (ou outro formato qualquer), processo conhecido como serialização.

Estender o RTTI significa fazer marcações em classes, métodos, campos ou propriedades, introduzindo características extras no elemento marcado. Como a marcação também é uma classe, podemos criar comportamentos auxiliares complexos.

O quadro abaixo mostra a declaração de uma classe simples que possui algumas marcações para permitir exportar seu conteúdo como um XML:
[TWXmlExportable('notaFiscal')]
TWNota=class
public
[TWXmlExportableNode('nro')]
Numero : longint;

[TWXmlExportableNode('serie')]
Serie : String;

[TWXmlExportableNode('observ')]
Observ: String;

[TWXmlExportableNode('vlrTotal')]
Valor: Double;

InfoNaoExport : String;
procedure GravaNota;
end;
Como podemos ver, a marcação de um elemento é feita colocando-se um par abre e fecha colchetes antes da declaração desse elemento, inserindo entre eles o nome da classe usada para marcá-lo. No exemplo, há duas classes de marcação sendo usadas - TWXmlExportable e TWXmlExportableNode, sendo que ambas aceitam um parâmetro do tipo string em seus construtores, o que também é fornecido na marcação do exemplo. Aqui, o parâmetro indica o nome da tag XML que conterá o elemento marcado. Observe que nem todos os campos foram marcados; apenas aqueles que eu quero exportar.

As classes de marcação são também chamadas de atributos e em Delphi têm obrigatoriamente que ser heranças da classe TCustomAttribute. A declaração das classes usadas no exemplo está na listagem a seguir:
TWXmlExportable = class(TCustomAttribute)
protected
FIsRoot: Boolean;
FTagName: String;
public
constructor Create (ATagName: String); virtual;

property TagName : String read FTagName;
property IsRoot: Boolean read FIsRoot;
end;

TWXmlExportableNode = class(TWXmlExportable)
public
constructor Create (ATagName: String); override;
end;

{ ... }

constructor TWXmlExportable.Create (ATagName: String);
begin
inherited Create;
FTagNAme := ATagName;
FIsRoot := true;
end;

constructor TWXmlExportableNode.Create (ATagName: String);
begin
inherited Create(ATagName);
FIsRoot := false;
end;
Introduzi nelas a propriedade IsRoot para poder diferenciar classes - que são elementos compostos por outros elementos - dos demais tipos. Assim, os campos de uma classe poderão ser aninhados em uma tag, enquanto cada campo de tipo básico é exportado em sua tag individual. Com isso, a exportação funcionará corretamente até mesmo com classes que possuam outros objetos exportáveis em sua estrutura.

Portanto, essas classes de marcação podem ser aplicadas livremente a quaisquer outros elementos no programa. A grande vantagem é que tal flexibilidade nos permite montar uma rotina genérica que seja capaz de exportar para XML qualquer objeto que contenha as marcações apropriadas. Da mesma forma que recuperamos a lista de métodos de um objeto no outro post, podemos interagir com os atributos desse objeto:
function TForm1.GetExportToXml (AObj: TObject) : TWXmlExportable;
var i : integer; lTipo: TRttiType;
begin
Result := Nil;
{_RttiCntxt é um TRttiContext instanciado em outro ponto do sistema }
lTipo := _RttiCntxt.GetType(AObj.ClassType);

{ Obtem os atributos - ou marcações - do objeto }
i := Length (lTipo.GetAttributes()) - 1;

{ Verifica se algum dos atributos é do tipo TWXmlExportable; isso significa que o objeto passado no parâmetro é "exportável" }
while (Result = Nil) And (i >= 0) do begin
if (lTipo.GetAttributes()[i] is TWXmlExportable) then
Result := (lTipo.GetAttributes()[i] As TWXmlExportable);
Dec (i);
end;
end;
A exportação para XML, então, passa a ser uma questão de levantar quais são os campos do objeto, determinar quais desses campos são "exportáveis" usando a mesma técnica acima, obter o nome da tag e o valor do campo. A função listada no quadro abaixo faz a exportação, seguindo esses passos:
function TForm1.ExportToXml (AObj: TObject) : string;
var lTipo: TRttiType;
lCampo: TRttiField;
lAttrib : TCustomAttribute;
lExpField, lExpObj : TWXmlExportable;
begin
Result := '';
lExpObj := GetExportToXml (AObj);

{ só continua se o objeto é "exportável" }
if lExpObj <> Nil then begin
lTipo := _RttiCntxt.GetType(AObj.ClassType);

{ Inicia a tag XML, obtendo o nome no atributo encontrado para o objeto }
Result := '<' + lExpObj.TagName + '>';

{ Quais são os campos nesse tipo de classe ? }
for lCampo in lTipo.GetFields() do begin
{ Analisa os atributos do campo pra ver quais são exportáveis }
for lAttrib in lCampo.GetAttributes() do
if (lAttrib is TWXmlExportable) then begin
lExpField := lAttrib As TWXmlExportable;
if (lExpField.IsRoot) then
{ campos do tipo classe são exportados recursivamente }
Result := Result + ExportToXml (lCampo.GetValue(AObj).AsObject)
else begin
{ campos de tipos simples apenas envolve com o nome da tag contido no atributo do campo }
Result := Result + '<' + lExpField.TagName + '>';
{ Obtem o valor atual do campo }
Result := Result + lCampo.GetValue(AObj).toString();
Result := Result + '</' + lExpField.TagName + '>';
end;
end;
end;

{ Encerra a tag desse objeto }
Result := Result + '</' + lExpObj.TagName + '>';
end;
end;
As informações sobre cada campo de uma classe são mantidas em instâncias de TRttiField. Além dos eventuais atributos associados ao campo, podemos descobrir com ela o nome do campo, seu tipo e sua visibilidade (público, protegido ou privado). Ela também fornece meios para recuperarmos ou modificarmos o valor do campo, conforme podemos ver no quadro anterior.

Agora, para exportar uma instância da classe de Nota precisamos apenas passá-la para a função acima. Na verdade, a função é genérica o suficiente pra ser capaz de exportar para XML instâncias de qualquer classe anotada com os atributos apresentados aqui.

Do jeito que esse recurso funciona, a classe de Nota apresentada no início do post pode focar na implementação das tarefas inerentes ao negócio, deixando a tarefa acessória de exportar os dados para outros pontos do sistema, favorecendo a coesão das classes na solução.