English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Aqui estão algumas dicas para lidar com a extração de arquivos de log. Suponhamos que estejamos vendo alguns extratos do Enterprise Splunk. Podemos explorar os dados com o Splunk. Ou podemos obter uma extração simples e brincar com esses dados no Python.
Executar diferentes experimentos em Python parece mais eficaz do que tentar exploratórias operações no Splunk. Principalmente porque podemos fazer qualquer coisa com os dados sem limites. Podemos criar modelos estatísticos complexos em um único lugar.
Teoricamente, podemos fazer muitas explorações no Splunk. Ele tem várias funções de relatórios e análise.
Mas...
Usar o Splunk requer a hipótese de que sabemos o que estamos procurando. Em muitos casos, não sabemos o que estamos procurando: estamos explorando. Pode haver algumas pistas de que alguns serviços RESTful estão processando lentamente, mas não é isso tudo. Como continuamos?
O primeiro passo é obter os dados originais no formato CSV. Como fazer isso?
Ler dados originais
Primeiro, usaremos algumas funções adicionais para embalar um objeto CSV.DictReader.
Os puristas orientados a objetos se oporiam a essa estratégia. “Por que não estender o DictReader?” eles perguntam. Não tenho uma resposta muito boa. Eu prefiro programação funcional e a ortogonalidade de componentes. Para um método puramente orientado a objetos, teríamos que usar uma mistura mais complexa para isso.
A estrutura geral que usamos para lidar com logs é a seguinte.
with open("somefile.csv") as source: rdr = csv.DictReader(source)
Isso nos permite ler extratos no formato CSV do Splunk. Podemos iterar sobre as linhas do leitor. Essa é a dica #1Isso não é muito difícil, mas eu gosto disso.
with open("somefile.csv") as source: rdr = csv.DictReader(source) for row in rdr: print( "{host} {ResponseTime} {source} {Service}".format_map(row) )
Podemos - Em certa medida - Relatar dados originais em um formato útil. Se quisermos melhorar a aparência da saída, podemos alterar a string de formato. Isso pode ser “{Host:30s} {Tempo de resposta:8s} {Fonte: s}” ou algo semelhante.
Filtragem
A situação mais comum é que extraímos muito, mas na verdade precisamos apenas de um subconjunto. Podemos alterar o filtro do Splunk, mas, antes de completarmos nossa exploração, o uso excessivo do filtro é irritante. Filtrar em Python é muito mais fácil. Assim que entendermos o que precisamos, podemos concluí-lo no Splunk.
with open("somefile.csv") as source: rdr = csv.DictReader(source) rdr_perf_log = (row for row in rdr if row['source'] == 'perf_log') for row in rdr_perf_log: print( "{host} {ResponseTime} {Service}".format_map(row) )
Já adicionamos uma expressão geradora para filtrar as linhas de origem, capazes de lidar com um subconjunto significativo.
Projeção
Em alguns casos, adicionaremos colunas de dados de origem adicionais que não queremos usar. Portanto, eliminaremos esses dados projetando cada linha.
Em princípio, o Splunk nunca gera colunas vazias. No entanto, os logs de API RESTful podem causar que o conjunto de dados contenha uma grande quantidade de nomes de colunas, que são chaves proxy baseadas em parte do URI da solicitação. Essas colunas conterão uma linha de dados de uma solicitação que usa esse proxy-chave. Para outras linhas, não há nada útil nessa coluna. Portanto, devemos remover essas colunas vazias.
Também podemos fazer isso com uma expressão geradora, mas ela ficará um pouco longa. A função geradora é mais fácil de ler.
def project(reader): for row in reader: yield {k:v for k,v in row.items() if v}
Já construímos uma nova linha de dicionário a partir de uma parte do leitor original. Podemos usá-lo para embalar a saída do nosso filtro.
with open("somefile.csv") as source: rdr = csv.DictReader(source) rdr_perf_log = (row for row in rdr if row['source'] == 'perf_log') for row in project(rdr_perf_log): print( "{host} {ResponseTime} {Service}".format_map(row) )
Isso reduzirá as colunas não usadas visíveis dentro da instrução for.
Alteração de símbolos
O símbolo row['source'] se tornará um pouco pesado. Usar types.SimpleNamespace é melhor do que usar um dicionário. Isso nos permite usar row.source.
Esta é uma técnica legal para criar algo mais útil.
rdr_ns= (types.SimpleNamespace(**row) forrowinreader)
Podemos empacotá-lo em uma sequência de passos como esta.
with open("somefile.csv") as source: rdr = csv.DictReader(source) rdr_perf_log = (row for row in rdr if row['source'] == 'perf_log') rdr_proj = project(rdr_perf_log) rdr_ns = (types.SimpleNamespace(**row) for row in rdr_proj) for row in rdr_ns: print( "{host} {ResponseTime} {Service}".format_map(vars(row)) )
Observe a pequena modificação no método format_map(). A partir das propriedades do SimpleNamespace, adicionamos a função vars() para extrair o dicionário.
Podemos usá-la para escrever uma função que mantenha a simetria sintática.
def ns_reader(reader): return (types.SimpleNamespace(**for row in reader)
De fato, podemos escrevê-lo como uma estrutura lambda usável como função
ns_reader = lambda reader: (types.SimpleNamespace(**for row in reader)
Embora o uso da função ns_reader()e do lambda ns_reader()sejam idênticos, é um pouco mais difícil escrever a documentação e os testes doctest para lambda. Por esse motivo, deve-se evitar a estrutura lambda.
Podemos usar map(lambda row:types.SimpleNamespace(** row),reader(). Alguns gostam dessa expressão de geração.
Podemos usar uma declaração for adequada e uma expressão yield interna, mas parece não haver benefício em escrever uma expressão grande a partir de algo pequeno.
Temos muitas opções, porque o Python oferece muitas funcionalidades de programação funcional. Embora não vejamos o Python frequentemente como um idioma funcional, temos várias maneiras de lidar com mapeamentos simples.
Mapeamento: dados de conversão e derivados
Muitas vezes, teremos uma lista de conversões de dados muito clara. Além disso, teremos uma lista cada vez maior de itens derivados. Os itens derivados serão dinâmicos e baseados em diferentes hipóteses que estamos testando. Sempre que tivermos um experimento ou problema, podemos mudar os dados derivados.
Cada um desses passos: filtrar, projetar, transformar e derivar são map-A fase "map" do pipeline reduce. Podemos criar algumas funções menores e aplicá-las no map(()). Porque estamos atualizando um objeto com estado, não podemos usar a função map() usual. Se quisermos implementar um estilo de programação funcional mais puro, usaremos um namedtuple imutável em vez de um SimpleNamespace mutável.
def convert(reader): for row in reader: row._time = datetime.datetime.strptime(row.Time, "%Y"-%m-%dT%H:%M:%S.%F%Z") row.response_time = float(row.ResponseTime) yield row
Durante nossa exploração, ajustaremos o corpo dessa função de conversão. Talvez começemos com algumas conversões e derivadas mais pequenas. Vamos continuar explorando com algumas perguntas como "esses são corretos?". Quando encontrarmos algo que não funciona, vamos removê-lo.
Nosso processo de tratamento geral é como segue:
with open("somefile.csv") as source: rdr = csv.DictReader(source) rdr_perf_log = (row for row in rdr if row['source'] == 'perf_log') rdr_proj = project(rdr_perf_log) rdr_ns = (types.SimpleNamespace(**row) for row in rdr_proj) rdr_converted = convert(rdr_ns) for row in rdr_converted: row.start_time = row._time - datetime.timedelta(seconds=row.response_time) row.service = some_mapping(row.Service) print( "{host:30s} {start_time:%H:%M:%S} {response_time:6.3f} {service}".format_map(vars(row)) )
Observe a mudança no sujeito da frase. A função convert() gera o valor que determinamos. Já adicionamos algumas variáveis adicionais no loop for,não podemos100% certo. Antes de atualizar a função convert(), vamos ver se eles são úteis (até mesmo corretos).
Redução
Em termos de redução, podemos adotar um processo de processamento ligeiramente diferente. Precisamos refatorar o exemplo anterior e torná-lo uma função geradora.
def converted_log(some_file): with open(some_file) as source: rdr = csv.DictReader(source) rdr_perf_log = (row for row in rdr if row['source'] == 'perf_log') rdr_proj = project(rdr_perf_log) rdr_ns = (types.SimpleNamespace(**row) for row in rdr_proj) rdr_converted = convert(rdr_ns) for row in rdr_converted: row.start_time = row._time - datetime.timedelta(seconds=row.response_time) row.service = some_mapping(row.Service) yield row
Em seguida, substituímos um yield por print().
Esta é outra parte da refatoração.
for row in converted_log("somefile.csv"): print( "{host:30s} {start_time:%H:%M:%S} {response_time:6.3f} {service}".format_map(vars(row)) )
Idealmente, todo nosso código de programação é assim. Usamos funções geradoras para gerar dados. A exibição final dos dados permanece completamente separada. Isso nos permite refatorar e alterar livremente o processamento.
Agora podemos fazer algumas coisas, como coletar as linhas em um objeto Counter() ou talvez calcular algumas informações estatísticas. Podemos usar defaultdict(list) para agrupar as linhas por serviço.
by_service= defaultdict(list) for row in converted_log("somefile.csv"): by_service[row.service] = row.response_time for svc in sorted(by_service): m = statistics.mean( by_service[svc] ) print( "{svc:15s} {m:.2f".format_map(vars()) )}
Decidimos criar objetos de lista específicos aqui. Podemos usar itertools para agrupar respostas por serviço tempo de resposta. Parece ser programação funcional correta, mas essa implementação no formato de programação funcional Pythonic indica algumas limitações. Ou devemos ordenar os dados (criar objetos de lista), ou criar listas ao agrupar dados. Para fazer várias estatísticas, agrupar dados através da criação de listas específicas geralmente é mais fácil.
Neste momento, estamos fazendo duas coisas, em vez de simplesmente imprimir o objeto de linha.
Crie algumas variáveis locais, como svc e m. Podemos facilmente adicionar mudanças ou medidas.
Ao usar a função vars() sem parâmetros, ela cria um dicionário a partir das variáveis locais.
O comportamento de usar vars() sem parâmetros é como o locals(), uma técnica conveniente. Permite-nos criar simplesmente qualquer variável local que desejamos e incluí-las na saída formatada. Podemos invadir várias estatísticas que achamos que podem estar relacionadas.
Embora nosso laço de processamento básico seja direcionado às linhas do converted_log ("somefile.csv"), podemos explorar muitas opções de processamento através de um script pequeno e fácil de modificar. Podemos explorar algumas hipóteses para determinar por que alguns processamentos RESTful API são lentos, enquanto outros são rápidos.
Resumo
O que foi mencionado acima é o que o editor apresentou aos amigos sobre a análise exploratória de dados em Python (funcional), esperando ajudar a todos. Se você tiver alguma dúvida, por favor, deixe um comentário, o editor responderá a todos a tempo. Também muito obrigado pelo apoio ao tutorial de gritos!
Declaração: O conteúdo deste artigo é extraído da Internet, pertence ao respectivo proprietário, foi submetido e carregado voluntariamente pelos usuários da Internet, o site não possui direitos de propriedade, não foi editado manualmente e não assume responsabilidades legais relacionadas. Se você encontrar conteúdo suspeito de infringência de direitos autorais, por favor, envie um e-mail para: notice#oldtoolbag.com (ao enviar e-mail, substitua # por @ para denunciar e forneça provas relevantes. Em caso de verificação, o site deletará imediatamente o conteúdo suspeito de infringência de direitos autorais.)