Como ver o histórico de consultas no SQL Server Management Studio

159

O histórico de consultas é armazenado em alguns arquivos de log? Se sim, você pode me dizer como encontrar a localização deles? Caso contrário, você pode me dar algum conselho sobre como vê-lo?

mstaniloiu
fonte
1
http://www.ssmstoolspack.com/ fornece uma janela de histórico, se é isso que você procura.
TI

Respostas:

226

[Como essa pergunta provavelmente será encerrada como duplicada.]

Se o SQL Server não tiver sido reiniciado (e o plano não tiver sido despejado etc.), você poderá encontrar a consulta no cache do plano.

SELECT t.[text]
FROM sys.dm_exec_cached_plans AS p
CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t
WHERE t.[text] LIKE N'%something unique about your query%';

Se você perdeu o arquivo porque o Management Studio travou, poderá encontrar arquivos de recuperação aqui:

C:\Users\<you>\Documents\SQL Server Management Studio\Backup Files\

Caso contrário, você precisará usar outra coisa a seguir para ajudá-lo a salvar seu histórico de consultas, como o SSMS Tools Pack, conforme mencionado na resposta de Ed Harper - embora não seja gratuito no SQL Server 2012+. Ou você pode configurar algum rastreamento leve filtrado no seu nome de login ou host (mas use um rastreamento do lado do servidor, não o Profiler, para isso).


Como comentou @ Nenad-Zivkovic, pode ser útil ingressar sys.dm_exec_query_statse solicitar last_execution_time:

SELECT t.[text], s.last_execution_time
FROM sys.dm_exec_cached_plans AS p
INNER JOIN sys.dm_exec_query_stats AS s
   ON p.plan_handle = s.plan_handle
CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t
WHERE t.[text] LIKE N'%something unique about your query%'
ORDER BY s.last_execution_time DESC;
Aaron Bertrand
fonte
9
Também pode ajudar a juntar-se em sys.dm_exec_query_statse pesquisar ou ordem porlast_execution_time
Nenad Zivkovic
Não funciona com o SQL Server 2000 e funciona a partir de sql server 2005
Durai Amuthan.H
@Duraiamuthan Bem, a pergunta é sobre o Management Studio, por isso deve ser seguro presumir 2005+. 2000 não possuía o Management Studio, tinha o Query Analyzer. 2000 também tem muitos anos sem suporte. Se você deseja resolver esse problema do SQL Server 2000, provavelmente deve fazer uma nova pergunta marcada com essa versão específica (se não existir uma duplicata, que eu não verifiquei).
Aaron Bertrand
1
@AaronBertrand Meu comentário é complementar à sua resposta
Ela
3
@AaronBertrand Você é um deus entre os homens.
Outro desenvolvedor,
49

Tarde, mas espero que útil, pois acrescenta mais detalhes ...

Não há como ver as consultas executadas no SSMS por padrão. Existem várias opções embora.

Lendo o log de transações - isso não é algo fácil de fazer porque está em formato proprietário. No entanto, se você precisar ver consultas que foram executadas historicamente (exceto SELECT), essa é a única maneira.

Você pode usar ferramentas de terceiros para isso, como ApexSQL Log e SQL Log Rescue (gratuito, mas apenas para o SQL 2000). Confira este tópico para obter mais detalhes aqui Explorador / Analisador de Log de Transações do SQL Server

Perfilador do SQL Server - mais adequado se você deseja iniciar a auditoria e não está interessado no que aconteceu anteriormente. Use filtros para selecionar apenas as transações necessárias. Caso contrário, você acabará com toneladas de dados muito rapidamente.

Rastreamento do SQL Server - mais adequado se você deseja capturar todos ou a maioria dos comandos e mantê-los no arquivo de rastreamento que pode ser analisado posteriormente.

Acionadores - mais adequado se você deseja capturar DML (exceto selecionar) e armazená-los em algum lugar no banco de dados

Djordje Kujundzic
fonte
Criar um arquivo de rastreamento no SQL Server Profiler ( msdn.microsoft.com/en-us/library/ms175047(v=sql.110).aspx ) usando o modelo padrão é uma boa maneira de monitorar as instruções executadas.
Javiniar.leonard 26/09/16
16

O pacote de ferramentas SSMS adiciona funcionalidade ao registro do histórico de execução, entre outras coisas.

Ed Harper
fonte
22
Não é mais livre como de SSMS 2012.
mattmc3
6

Como outros observaram, você pode usar o SQL Profiler, mas também pode aproveitar sua funcionalidade por meio de procedimentos armazenados do sistema sp_trace_ *. Por exemplo, esse snippet SQL (pelo menos em 2000; acho que é o mesmo para o SQL 2008, mas você terá que verificar duas vezes) capturas RPC:Completede SQL:BatchCompletedeventos para todas as consultas que demoram mais de 10 segundos para executar e salvar a saída em um arquivo de rastreamento que você pode abrir no SQL Profiler posteriormente:

DECLARE @TraceID INT
DECLARE @ON BIT
DECLARE @RetVal INT
SET @ON = 1

exec @RetVal = sp_trace_create @TraceID OUTPUT, 2, N'Y:\TraceFile.trc'
print 'This trace is Trace ID = ' + CAST(@TraceID AS NVARCHAR)
print 'Return value = ' + CAST(@RetVal AS NVARCHAR)
-- 10 = RPC:Completed
exec sp_trace_setevent @TraceID, 10, 1, @ON     -- Textdata
exec sp_trace_setevent @TraceID, 10, 3, @ON     -- DatabaseID
exec sp_trace_setevent @TraceID, 10, 12, @ON        -- SPID
exec sp_trace_setevent @TraceID, 10, 13, @ON        -- Duration
exec sp_trace_setevent @TraceID, 10, 14, @ON        -- StartTime
exec sp_trace_setevent @TraceID, 10, 15, @ON        -- EndTime

-- 12 = SQL:BatchCompleted
exec sp_trace_setevent @TraceID, 12, 1, @ON     -- Textdata
exec sp_trace_setevent @TraceID, 12, 3, @ON     -- DatabaseID
exec sp_trace_setevent @TraceID, 12, 12, @ON        -- SPID
exec sp_trace_setevent @TraceID, 12, 13, @ON        -- Duration
exec sp_trace_setevent @TraceID, 12, 14, @ON        -- StartTime
exec sp_trace_setevent @TraceID, 12, 15, @ON        -- EndTime

-- Filter for duration [column 13] greater than [operation 2] 10 seconds (= 10,000ms)
declare @duration bigint
set @duration = 10000
exec sp_trace_setfilter @TraceID, 13, 0, 2, @duration

Você pode encontrar o ID de cada evento de rastreamento, colunas etc. nos Manuais Online; basta procurar os sprocs sp_trace_create , sp_trace_setevent e sp_trace_setfiler . Você pode controlar o rastreamento da seguinte maneira:

exec sp_trace_setstatus 15, 0       -- Stop the trace
exec sp_trace_setstatus 15, 1       -- Start the trace
exec sp_trace_setstatus 15, 2       -- Close the trace file and delete the trace settings

... onde '15' é o ID de rastreamento (conforme relatado por sp_trace_create, que o primeiro script inicia acima).

Você pode verificar para ver com que rastreamentos estão sendo executados:

select * from ::fn_trace_getinfo(default)

A única coisa que direi com cautela - não sei quanta carga isso colocará no seu sistema; ele adicionará alguns, mas o tamanho desses "alguns" provavelmente depende da ocupação do servidor.

Chris J
fonte
Código útil. Só funcionou para mim quando removi a extensão de arquivo ".trc".
Steve Smith
5

O sistema não registra consultas dessa maneira. Se você sabe que deseja fazer isso com antecedência, pode usar o SQL Profiler para registrar o que está chegando e rastrear consultas durante o tempo em que o Profiler estiver em execução.

Tiramina
fonte
5

Eu uso a consulta abaixo para rastrear a atividade do aplicativo em um servidor SQL que não possui o gerenciador de perfis de rastreamento ativado. O método usa o Query Store (SQL Server 2016 ou superior) em vez das DMVs. Isso oferece uma melhor capacidade de analisar dados históricos, bem como pesquisas mais rápidas. É muito eficiente capturar consultas de execução curta que não podem ser capturadas por sp_who / sp_whoisactive.

/* Adjust script to your needs.
    Run full script (F5) -> Interact with UI -> Run full script again (F5)
    Output will contain the queries completed in that timeframe.
*/

/* Requires Query Store to be enabled:
    ALTER DATABASE <db> SET QUERY_STORE = ON
    ALTER DATABASE <db> SET QUERY_STORE (OPERATION_MODE = READ_WRITE, MAX_STORAGE_SIZE_MB = 100000)
*/

USE <db> /* Select your DB */

IF OBJECT_ID('tempdb..#lastendtime') IS NULL
    SELECT GETUTCDATE() AS dt INTO #lastendtime
ELSE IF NOT EXISTS (SELECT * FROM #lastendtime)
    INSERT INTO #lastendtime VALUES (GETUTCDATE()) 

;WITH T AS (
SELECT 
    DB_NAME() AS DBName
    , s.name + '.' + o.name AS ObjectName
    , qt.query_sql_text
    , rs.runtime_stats_id
    , p.query_id
    , p.plan_id
    , CAST(p.last_execution_time AS DATETIME) AS last_execution_time
    , CASE WHEN p.last_execution_time > #lastendtime.dt THEN 'X' ELSE '' END AS New
    , CAST(rs.last_duration / 1.0e6 AS DECIMAL(9,3)) last_duration_s
    , rs.count_executions
    , rs.last_rowcount
    , rs.last_logical_io_reads
    , rs.last_physical_io_reads
    , q.query_parameterization_type_desc
FROM (
    SELECT *, ROW_NUMBER() OVER (PARTITION BY plan_id, runtime_stats_id ORDER BY runtime_stats_id DESC) AS recent_stats_in_current_priod
    FROM sys.query_store_runtime_stats 
    ) AS rs
INNER JOIN sys.query_store_runtime_stats_interval AS rsi ON rsi.runtime_stats_interval_id = rs.runtime_stats_interval_id
INNER JOIN sys.query_store_plan AS p ON p.plan_id = rs.plan_id
INNER JOIN sys.query_store_query AS q ON q.query_id = p.query_id
INNER JOIN sys.query_store_query_text AS qt ON qt.query_text_id = q.query_text_id
LEFT OUTER JOIN sys.objects AS o ON o.object_id = q.object_id
LEFT OUTER JOIN sys.schemas AS s ON s.schema_id = o.schema_id
CROSS APPLY #lastendtime
WHERE rsi.start_time <= GETUTCDATE() AND GETUTCDATE() < rsi.end_time
    AND recent_stats_in_current_priod = 1
    /* Adjust your filters: */
    -- AND (s.name IN ('<myschema>') OR s.name IS NULL)
UNION
SELECT NULL,NULL,NULL,NULL,NULL,NULL,dt,NULL,NULL,NULL,NULL,NULL,NULL, NULL
FROM #lastendtime
)
SELECT * FROM T
WHERE T.query_sql_text IS NULL OR T.query_sql_text NOT LIKE '%#lastendtime%' -- do not show myself
ORDER BY last_execution_time DESC

TRUNCATE TABLE #lastendtime
INSERT INTO #lastendtime VALUES (GETUTCDATE()) 
Martin Thøgersen
fonte
4
SELECT deqs.last_execution_time AS [Time], dest.text AS [Query], dest.*
FROM sys.dm_exec_query_stats AS deqs
CROSS APPLY sys.dm_exec_sql_text(deqs.sql_handle) AS dest
WHERE dest.dbid = DB_ID('msdb')
ORDER BY deqs.last_execution_time DESC

Isso deve mostrar a hora e a data em que uma consulta foi executada

Jose Ortiz
fonte
3

Você pode monitorar consultas SQL pelo SQL Profiler, se precisar

Arsen Mkrtchyan
fonte
2

O histórico de consultas pode ser visualizado usando as visualizações do sistema:

  1. sys.dm_exec_query_stats
  2. sys.dm_exec_sql_text
  3. sys.dm_exec_query_plan

Por exemplo, usando a seguinte consulta:

select  top(100)
        creation_time,
        last_execution_time,
        execution_count,
        total_worker_time/1000 as CPU,
        convert(money, (total_worker_time))/(execution_count*1000)as [AvgCPUTime],
        qs.total_elapsed_time/1000 as TotDuration,
        convert(money, (qs.total_elapsed_time))/(execution_count*1000)as [AvgDur],
        total_logical_reads as [Reads],
        total_logical_writes as [Writes],
        total_logical_reads+total_logical_writes as [AggIO],
        convert(money, (total_logical_reads+total_logical_writes)/(execution_count + 0.0)) as [AvgIO],
        [sql_handle],
        plan_handle,
        statement_start_offset,
        statement_end_offset,
        plan_generation_num,
        total_physical_reads,
        convert(money, total_physical_reads/(execution_count + 0.0)) as [AvgIOPhysicalReads],
        convert(money, total_logical_reads/(execution_count + 0.0)) as [AvgIOLogicalReads],
        convert(money, total_logical_writes/(execution_count + 0.0)) as [AvgIOLogicalWrites],
        query_hash,
        query_plan_hash,
        total_rows,
        convert(money, total_rows/(execution_count + 0.0)) as [AvgRows],
        total_dop,
        convert(money, total_dop/(execution_count + 0.0)) as [AvgDop],
        total_grant_kb,
        convert(money, total_grant_kb/(execution_count + 0.0)) as [AvgGrantKb],
        total_used_grant_kb,
        convert(money, total_used_grant_kb/(execution_count + 0.0)) as [AvgUsedGrantKb],
        total_ideal_grant_kb,
        convert(money, total_ideal_grant_kb/(execution_count + 0.0)) as [AvgIdealGrantKb],
        total_reserved_threads,
        convert(money, total_reserved_threads/(execution_count + 0.0)) as [AvgReservedThreads],
        total_used_threads,
        convert(money, total_used_threads/(execution_count + 0.0)) as [AvgUsedThreads],
        case 
            when sql_handle IS NULL then ' '
            else(substring(st.text,(qs.statement_start_offset+2)/2,(
                case
                    when qs.statement_end_offset =-1 then len(convert(nvarchar(MAX),st.text))*2      
                    else qs.statement_end_offset    
                end - qs.statement_start_offset)/2  ))
        end as query_text,
        db_name(st.dbid) as database_name,
        object_schema_name(st.objectid, st.dbid)+'.'+object_name(st.objectid, st.dbid) as [object_name],
        sp.[query_plan]
from sys.dm_exec_query_stats as qs with(readuncommitted)
cross apply sys.dm_exec_sql_text(qs.[sql_handle]) as st
cross apply sys.dm_exec_query_plan(qs.[plan_handle]) as sp
WHERE st.[text] LIKE '%query%'

As consultas em execução atuais podem ser vistas usando o seguinte script:

select ES.[session_id]
      ,ER.[blocking_session_id]
      ,ER.[request_id]
      ,ER.[start_time]
      ,DateDiff(second, ER.[start_time], GetDate()) as [date_diffSec]
      , COALESCE(
                    CAST(NULLIF(ER.[total_elapsed_time] / 1000, 0) as BIGINT)
                   ,CASE WHEN (ES.[status] <> 'running' and isnull(ER.[status], '')  <> 'running') 
                            THEN  DATEDIFF(ss,0,getdate() - nullif(ES.[last_request_end_time], '1900-01-01T00:00:00.000'))
                    END
                ) as [total_time, sec]
      , CAST(NULLIF((CAST(ER.[total_elapsed_time] as BIGINT) - CAST(ER.[wait_time] AS BIGINT)) / 1000, 0 ) as bigint) as [work_time, sec]
      , CASE WHEN (ER.[status] <> 'running' AND ISNULL(ER.[status],'') <> 'running') 
                THEN  DATEDIFF(ss,0,getdate() - nullif(ES.[last_request_end_time], '1900-01-01T00:00:00.000'))
        END as [sleep_time, sec] --Время сна в сек
      , NULLIF( CAST((ER.[logical_reads] + ER.[writes]) * 8 / 1024 as numeric(38,2)), 0) as [IO, MB]
      , CASE  ER.transaction_isolation_level
        WHEN 0 THEN 'Unspecified'
        WHEN 1 THEN 'ReadUncommited'
        WHEN 2 THEN 'ReadCommited'
        WHEN 3 THEN 'Repetable'
        WHEN 4 THEN 'Serializable'
        WHEN 5 THEN 'Snapshot'
        END as [transaction_isolation_level_desc]
      ,ER.[status]
      ,ES.[status] as [status_session]
      ,ER.[command]
      ,ER.[percent_complete]
      ,DB_Name(coalesce(ER.[database_id], ES.[database_id])) as [DBName]
      , SUBSTRING(
                    (select top(1) [text] from sys.dm_exec_sql_text(ER.[sql_handle]))
                  , ER.[statement_start_offset]/2+1
                  , (
                        CASE WHEN ((ER.[statement_start_offset]<0) OR (ER.[statement_end_offset]<0))
                                THEN DATALENGTH ((select top(1) [text] from sys.dm_exec_sql_text(ER.[sql_handle])))
                             ELSE ER.[statement_end_offset]
                        END
                        - ER.[statement_start_offset]
                    )/2 +1
                 ) as [CURRENT_REQUEST]
      ,(select top(1) [text] from sys.dm_exec_sql_text(ER.[sql_handle])) as [TSQL]
      ,(select top(1) [objectid] from sys.dm_exec_sql_text(ER.[sql_handle])) as [objectid]
      ,(select top(1) [query_plan] from sys.dm_exec_query_plan(ER.[plan_handle])) as [QueryPlan]
      ,NULL as [event_info]--(select top(1) [event_info] from sys.dm_exec_input_buffer(ES.[session_id], ER.[request_id])) as [event_info]
      ,ER.[wait_type]
      ,ES.[login_time]
      ,ES.[host_name]
      ,ES.[program_name]
      ,cast(ER.[wait_time]/1000 as decimal(18,3)) as [wait_timeSec]
      ,ER.[wait_time]
      ,ER.[last_wait_type]
      ,ER.[wait_resource]
      ,ER.[open_transaction_count]
      ,ER.[open_resultset_count]
      ,ER.[transaction_id]
      ,ER.[context_info]
      ,ER.[estimated_completion_time]
      ,ER.[cpu_time]
      ,ER.[total_elapsed_time]
      ,ER.[scheduler_id]
      ,ER.[task_address]
      ,ER.[reads]
      ,ER.[writes]
      ,ER.[logical_reads]
      ,ER.[text_size]
      ,ER.[language]
      ,ER.[date_format]
      ,ER.[date_first]
      ,ER.[quoted_identifier]
      ,ER.[arithabort]
      ,ER.[ansi_null_dflt_on]
      ,ER.[ansi_defaults]
      ,ER.[ansi_warnings]
      ,ER.[ansi_padding]
      ,ER.[ansi_nulls]
      ,ER.[concat_null_yields_null]
      ,ER.[transaction_isolation_level]
      ,ER.[lock_timeout]
      ,ER.[deadlock_priority]
      ,ER.[row_count]
      ,ER.[prev_error]
      ,ER.[nest_level]
      ,ER.[granted_query_memory]
      ,ER.[executing_managed_code]
      ,ER.[group_id]
      ,ER.[query_hash]
      ,ER.[query_plan_hash]
      ,EC.[most_recent_session_id]
      ,EC.[connect_time]
      ,EC.[net_transport]
      ,EC.[protocol_type]
      ,EC.[protocol_version]
      ,EC.[endpoint_id]
      ,EC.[encrypt_option]
      ,EC.[auth_scheme]
      ,EC.[node_affinity]
      ,EC.[num_reads]
      ,EC.[num_writes]
      ,EC.[last_read]
      ,EC.[last_write]
      ,EC.[net_packet_size]
      ,EC.[client_net_address]
      ,EC.[client_tcp_port]
      ,EC.[local_net_address]
      ,EC.[local_tcp_port]
      ,EC.[parent_connection_id]
      ,EC.[most_recent_sql_handle]
      ,ES.[host_process_id]
      ,ES.[client_version]
      ,ES.[client_interface_name]
      ,ES.[security_id]
      ,ES.[login_name]
      ,ES.[nt_domain]
      ,ES.[nt_user_name]
      ,ES.[memory_usage]
      ,ES.[total_scheduled_time]
      ,ES.[last_request_start_time]
      ,ES.[last_request_end_time]
      ,ES.[is_user_process]
      ,ES.[original_security_id]
      ,ES.[original_login_name]
      ,ES.[last_successful_logon]
      ,ES.[last_unsuccessful_logon]
      ,ES.[unsuccessful_logons]
      ,ES.[authenticating_database_id]
      ,ER.[sql_handle]
      ,ER.[statement_start_offset]
      ,ER.[statement_end_offset]
      ,ER.[plan_handle]
      ,NULL as [dop]--ER.[dop]
      ,coalesce(ER.[database_id], ES.[database_id]) as [database_id]
      ,ER.[user_id]
      ,ER.[connection_id]
from sys.dm_exec_requests ER with(readuncommitted)
right join sys.dm_exec_sessions ES with(readuncommitted)
on ES.session_id = ER.session_id 
left join sys.dm_exec_connections EC  with(readuncommitted)
on EC.session_id = ES.session_id
where ER.[status] in ('suspended', 'running', 'runnable')
or exists (select top(1) 1 from sys.dm_exec_requests as ER0 where ER0.[blocking_session_id]=ES.[session_id])

Essa solicitação exibe todas as solicitações ativas e todas as solicitações que bloqueiam explicitamente solicitações ativas.

Todos esses e outros scripts úteis são implementados como representações no banco de dados SRV , que é distribuído livremente. Por exemplo, o primeiro script veio da exibição [inf]. [VBigQuery] e o segundo veio da exibição [inf]. [VRequests] .

Existem também várias soluções de terceiros para o histórico de consultas. Eu uso o Query Manager do Dbeaver : insira a descrição da imagem aqui e o Query Execution History do SQL Tools , incorporado no SSMS : insira a descrição da imagem aqui

Evgeniy Gribkov
fonte
1

Esse recurso não existe imediatamente no SSMS.

Se você estiver usando o SSMS 18 ou mais recente, tente o SSMSPlus.

Possui um recurso de histórico de consultas.

https://github.com/akarzazi/SSMSPlus

Disclaimer: Eu sou o autor.

Adelos
fonte
0

você pode usar "Gerar script automaticamente a cada salvamento", se estiver usando o Management Studio. Isso certamente não é log. Verifique se útil para você ..;)

Pecado
fonte
0

Se as consultas nas quais você está interessado são dinâmicas e falham intermitentemente, você pode registrar o SQL e a data e hora do usuário em uma tabela no momento em que a instrução dinâmica é criada. Isso seria feito caso a caso, pois requer programação específica e leva um tempo de processamento extra, assim como somente nas poucas consultas com as quais você está mais preocupado. Mas ter um registro das instruções específicas executadas pode realmente ajudar quando você está tentando descobrir por que ela falha apenas uma vez por mês. As consultas dinâmicas são difíceis de testar completamente e, às vezes, você obtém um valor de entrada específico que simplesmente não funciona e fazer esse log no momento em que o SQL é criado geralmente é a melhor maneira de ver o que especificamente não estava no sql que foi criado.

HLGEM
fonte
0

Um método ligeiramente pronto para uso seria criar uma solução no AutoHotKey. Eu uso isso, e não é perfeito, mas funciona e é gratuito. Essencialmente, esse script atribui uma tecla de atalho a CTRL+ SHIFT+, Rque copia o SQL selecionado no SSMS ( CTRL+ C), salva um arquivo SQL de carimbo de data e, em seguida, executa a consulta destacada ( F5). Se você não está acostumado com scripts AHK, o ponto e vírgula inicial é um comentário.

;CTRL+SHIFT+R to run a query that is first saved off
^+r::
;Copy
Send, ^c
; Set variables
EnvGet, HomeDir, USERPROFILE
FormatTime, DateString,,yyyyMMdd
FormatTime, TimeString,,hhmmss
; Make a spot to save the clipboard
FileCreateDir %HomeDir%\Documents\sqlhist\%DateString%
FileAppend, %Clipboard%, %HomeDir%\Documents\sqlhist\%DateString%\%TimeString%.sql
; execute the query
Send, {f5}
Return

As maiores limitações são que esse script não funcionará se você clicar em "Executar" em vez de usar o atalho de teclado, e esse script não salvará todo o arquivo - apenas o texto selecionado. Mas você sempre pode modificar o script para executar a consulta e selecionar tudo ( CTRL+ A) antes da cópia / salvar.

O uso de um editor moderno com recursos "localização em arquivos" permitirá pesquisar seu histórico SQL. Você pode até gostar e juntar seus arquivos em um banco de dados SQLite3 para consultar suas consultas.

mattmc3
fonte