Tutorial: Detetar e analisar anomalias usando os recursos de aprendizado de máquina KQL no Azure Monitor
A Kusto Query Language (KQL) inclui operadores de aprendizado de máquina, funções e plugins para análise de séries temporais, deteção de anomalias, previsão e análise de causa raiz. Use esses recursos do KQL para executar análises avançadas de dados no Azure Monitor sem a sobrecarga de exportar dados para ferramentas externas de aprendizado de máquina.
Neste tutorial, irá aprender a:
- Criar uma série temporal
- Identificar anomalias em uma série temporal
- Ajuste as configurações de deteção de anomalias para refinar os resultados
- Analise a causa raiz das anomalias
Nota
Este tutorial fornece links para um ambiente de demonstração do Log Analytics no qual você pode executar os exemplos de consulta KQL. Os dados no ambiente de demonstração são dinâmicos, portanto, os resultados da consulta não são os mesmos que os resultados da consulta mostrados neste artigo. No entanto, você pode implementar as mesmas consultas KQL e entidades em seu próprio ambiente e todas as ferramentas do Azure Monitor que usam o KQL.
Pré-requisitos
- Uma conta do Azure com uma subscrição ativa. Crie uma conta gratuitamente.
- Um espaço de trabalho com dados de log.
Permissões necessárias
Você deve ter Microsoft.OperationalInsights/workspaces/query/*/read
permissões para os espaços de trabalho do Log Analytics consultados, conforme fornecido pela função interna do Log Analytics Reader, por exemplo.
Criar uma série temporal
Use o operador KQL make-series
para criar uma série temporal.
Vamos criar uma série temporal com base em logs na tabela Uso, que contém informações sobre a quantidade de dados que cada tabela em um espaço de trabalho ingere a cada hora, incluindo dados faturáveis e não faturáveis.
Esta consulta usa make-series
para mapear a quantidade total de dados faturáveis ingeridos por cada tabela no espaço de trabalho todos os dias, nos últimos 21 dias:
Clique para executar a consulta
let starttime = 21d; // The start date of the time series, counting back from the current date
let endtime = 0d; // The end date of the time series, counting back from the current date
let timeframe = 1d; // How often to sample data
Usage // The table we’re analyzing
| where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime))) // Time range for the query, beginning at 12:00 AM of the first day and ending at 12:00 AM of the last day in the time range
| where IsBillable == "true" // Include only billable data in the result set
| make-series ActualUsage=sum(Quantity) default = 0 on TimeGenerated from startofday(ago(starttime)) to startofday(ago(endtime)) step timeframe by DataType // Creates the time series, listed by data type
| render timechart // Renders results in a timechart
No gráfico resultante, você pode ver claramente algumas anomalias - por exemplo, nos AzureDiagnostics
tipos de dados e SecurityEvent
:
Em seguida, usaremos uma função KQL para listar todas as anomalias em uma série temporal.
Nota
Para obter mais informações sobre make-series
sintaxe e uso, consulte operador make-series.
Encontrar anomalias numa série temporal
A series_decompose_anomalies()
função toma uma série de valores como entrada e extrai anomalias.
Vamos dar o conjunto de resultados da nossa consulta de série temporal como entrada para a series_decompose_anomalies()
função:
Clique para executar a consulta
let starttime = 21d; // Start date for the time series, counting back from the current date
let endtime = 0d; // End date for the time series, counting back from the current date
let timeframe = 1d; // How often to sample data
Usage // The table we’re analyzing
| where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime))) // Time range for the query, beginning at 12:00 AM of the first day and ending at 12:00 AM of the last day in the time range
| where IsBillable == "true" // Includes only billable data in the result set
| make-series ActualUsage=sum(Quantity) default = 0 on TimeGenerated from startofday(ago(starttime)) to startofday(ago(endtime)) step timeframe by DataType // Creates the time series, listed by data type
| extend(Anomalies, AnomalyScore, ExpectedUsage) = series_decompose_anomalies(ActualUsage) // Scores and extracts anomalies based on the output of make-series
| mv-expand ActualUsage to typeof(double), TimeGenerated to typeof(datetime), Anomalies to typeof(double),AnomalyScore to typeof(double), ExpectedUsage to typeof(long) // Expands the array created by series_decompose_anomalies()
| where Anomalies != 0 // Returns all positive and negative deviations from expected usage
| project TimeGenerated,ActualUsage,ExpectedUsage,AnomalyScore,Anomalies,DataType // Defines which columns to return
| sort by abs(AnomalyScore) desc // Sorts results by anomaly score in descending ordering
Esta consulta retorna todas as anomalias de uso para todas as tabelas nas últimas três semanas:
Observando os resultados da consulta, você pode ver que a função:
- Calcula um uso diário esperado para cada tabela.
- Compara o uso diário real com o uso esperado.
- Atribui uma pontuação de anomalia a cada ponto de dados, indicando a extensão do desvio do uso real do uso esperado.
- Identifica anomalias positivas (
1
) e negativas (-1
) em cada tabela.
Nota
Para obter mais informações sobre series_decompose_anomalies()
sintaxe e uso, consulte series_decompose_anomalies().
Ajuste as configurações de deteção de anomalias para refinar os resultados
É uma boa prática revisar os resultados iniciais da consulta e fazer ajustes na consulta, se necessário. Discrepâncias nos dados de entrada podem afetar o aprendizado da função, e você pode precisar ajustar as configurações de deteção de anomalias da função para obter resultados mais precisos.
Filtre os series_decompose_anomalies()
resultados da consulta por anomalias no AzureDiagnostics
tipo de dados:
Os resultados mostram duas anomalias em 14 e 15 de junho. Compare estes resultados com o gráfico da nossa primeira make-series
consulta, onde pode ver outras anomalias nos dias 27 e 28 de maio:
A diferença nos resultados ocorre porque a series_decompose_anomalies()
função pontua anomalias em relação ao valor de uso esperado, que a função calcula com base no intervalo completo de valores na série de entrada.
Para obter resultados mais refinados da função, exclua o uso no dia 15 de junho - que é um ponto fora da curva em comparação com os outros valores da série - do processo de aprendizagem da função.
A sintaxe da series_decompose_anomalies()
função é:
series_decompose_anomalies (Series[Threshold,Seasonality,Trend,Test_points,AD_method,Seasonality_threshold])
Test_points
Especifica o número de pontos no final da série a excluir do processo de aprendizagem (regressão).
Para excluir o último ponto de dados, defina Test_points
como 1
:
Clique para executar a consulta
let starttime = 21d; // Start date for the time series, counting back from the current date
let endtime = 0d; // End date for the time series, counting back from the current date
let timeframe = 1d; // How often to sample data
Usage // The table we’re analyzing
| where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime))) // Time range for the query, beginning at 12:00 AM of the first day and ending at 12:00 AM of the last day in the time range
| where IsBillable == "true" // Includes only billable data in the result set
| make-series ActualUsage=sum(Quantity) default = 0 on TimeGenerated from startofday(ago(starttime)) to startofday(ago(endtime)) step timeframe by DataType // Creates the time series, listed by data type
| extend(Anomalies, AnomalyScore, ExpectedUsage) = series_decompose_anomalies(ActualUsage,1.5,-1,'avg',1) // Scores and extracts anomalies based on the output of make-series, excluding the last value in the series - the Threshold, Seasonality, and Trend input values are the default values for the function
| mv-expand ActualUsage to typeof(double), TimeGenerated to typeof(datetime), Anomalies to typeof(double),AnomalyScore to typeof(double), ExpectedUsage to typeof(long) // Expands the array created by series_decompose_anomalies()
| where Anomalies != 0 // Returns all positive and negative deviations from expected usage
| project TimeGenerated,ActualUsage,ExpectedUsage,AnomalyScore,Anomalies,DataType // Defines which columns to return
| sort by abs(AnomalyScore) desc // Sorts results by anomaly score in descending ordering
Filtre os resultados para o tipo de AzureDiagnostics
dados:
Todas as anomalias no gráfico da nossa primeira make-series
consulta agora aparecem no conjunto de resultados.
Analise a causa raiz das anomalias
Comparar valores esperados com valores anômalos ajuda a entender a causa das diferenças entre os dois conjuntos.
O plugin KQL diffpatterns()
compara dois conjuntos de dados da mesma estrutura e encontra padrões que caracterizam as diferenças entre os dois conjuntos de dados.
Esta consulta compara o AzureDiagnostics
uso em 15 de junho, o outlier extremo em nosso exemplo, com o uso da tabela em outros dias:
Clique para executar a consulta
let starttime = 21d; // Start date for the time series, counting back from the current date
let endtime = 0d; // End date for the time series, counting back from the current date
let anomalyDate = datetime_add('day',-1, make_datetime(startofday(ago(endtime)))); // Start of day of the anomaly date, which is the last full day in the time range in our example (you can replace this with a specific hard-coded anomaly date)
AzureDiagnostics
| extend AnomalyDate = iff(startofday(TimeGenerated) == anomalyDate, "AnomalyDate", "OtherDates") // Adds calculated column called AnomalyDate, which splits the result set into two data sets – AnomalyDate and OtherDates
| where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime))) // Defines the time range for the query
| project AnomalyDate, Resource // Defines which columns to return
| evaluate diffpatterns(AnomalyDate, "OtherDates", "AnomalyDate") // Compares usage on the anomaly date with the regular usage pattern
A consulta identifica cada entrada na tabela como ocorrendo em AnomalyDate (15 de junho) ou OtherDates. O diffpatterns()
plugin então divide esses conjuntos de dados - chamados A (OtherDates em nosso exemplo) e B (AnomalyDate em nosso exemplo) - e retorna alguns padrões que contribuem para as diferenças nos dois conjuntos:
Observando os resultados da consulta, você pode ver as seguintes diferenças:
- Há 24.892.147 instâncias de ingestão do recurso CH1-GEARAMAAKS em todos os outros dias no intervalo de tempo de consulta, e nenhuma ingestão de dados desse recurso em 15 de junho. Os dados do recurso CH1-GEARAMAAKS representam 73,36% da ingestão total em outros dias no intervalo de tempo de consulta e 0% da ingestão total em 15 de junho.
- Há 2.168.448 instâncias de ingestão do recurso NSG-TESTSQLMI519 em todos os outros dias no intervalo de tempo de consulta e 110.544 instâncias de ingestão desse recurso em 15 de junho. Os dados do recurso NSG-TESTSQLMI519 representam 6,39% da ingestão total em outros dias no intervalo de tempo de consulta e 25,61% da ingestão em 15 de junho.
Observe que, em média, há 108.422 casos de ingestão do recurso NSG-TESTSQLMI519 durante os 20 dias que compõem o período dos outros dias (dividir 2.168.448 por 20). Portanto, a ingestão do recurso NSG-TESTSQLMI519 em 15 de junho não é significativamente diferente da ingestão deste recurso em outros dias. No entanto, como não há ingestão de CH1-GEARAMAAKS em 15 de junho, a ingestão de NSG-TESTSQLMI519 representa uma porcentagem significativamente maior da ingestão total na data da anomalia em comparação com outros dias.
A coluna PercentDiffAB mostra a diferença absoluta de pontos percentuais entre A e B (|PercentA - PercentB|), que é a principal medida da diferença entre os dois conjuntos. Por padrão, o plug-in retorna uma diffpatterns()
diferença de mais de 5% entre os dois conjuntos de dados, mas você pode ajustar esse limite. Por exemplo, para retornar apenas diferenças de 20% ou mais entre os dois conjuntos de dados, você pode definir | evaluate diffpatterns(AnomalyDate, "OtherDates", "AnomalyDate", "~", 0.20)
na consulta acima. A consulta agora retorna apenas um resultado:
Nota
Para obter mais informações sobre diffpatterns()
sintaxe e uso, consulte plug-in de padrões de comparação.
Próximos passos
Saiba mais sobre: