English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Aquí hay algunos trucos para manejar la extracción de archivos de registro. Supongamos que estamos viendo algunos extractos de Enterprise Splunk. Podemos explorar los datos con Splunk. O podemos obtener una extracción simple y jugar con estos datos en Python.
Ejecutar diferentes experimentos en Python parece más efectivo que intentar realizar estas operaciones exploratorias en Splunk. Principalmente porque podemos hacer cualquier cosa con los datos sin limitaciones. Podemos crear modelos estadísticos muy complejos en un solo lugar.
Teóricamente, podemos hacer muchas exploraciones en Splunk. Tiene varias funciones de informes y análisis.
Pero...
Usar Splunk requiere asumir que sabemos lo que estamos buscando. En muchos casos, no sabemos lo que estamos buscando: estamos explorando. Puede haber algunas señales de que algunos API RESTful procesan lentamente, pero no es solo eso. ¿Cómo seguimos?
El primer paso es obtener los datos originales en formato CSV. ¿Qué hacer?
Leer datos originales
Primero, vamos a encapsular un objeto CSV.DictReader con algunas funciones adicionales.
Los puristas de la programación orientada a objetos podrían oponerse a esta estrategia. “¿Por qué no expandir DictReader?” preguntan. No tengo una buena respuesta. Me inclino hacia la programación funcional y la ortogonalidad de los componentes. Para un método puramente orientado a objetos, tendríamos que usar una mezcla más compleja para lograr esto.
El marco general que usamos para procesar los registros de log es el siguiente.
with open("somefile.csv") as source: rdr = csv.DictReader(source)
Esto nos permite leer extractos de Splunk en formato CSV. Podemos iterar sobre las líneas del lector. Esto es el truco #1Esto no es muy complicado, pero me gusta.
with open("somefile.csv") as source: rdr = csv.DictReader(source) for row in rdr: print( "{host} {ResponseTime} {source} {Service}".format_map(row) )
Podemos - En cierta medida - Reportar los datos originales en un formato útil. Si queremos embellecer la salida, podemos cambiar la cadena de formato. Podría ser “{host:30s} {tiempo de respuesta:8s} {fuente: s}” o algo similar.
Filtrar
En la mayoría de los casos, hemos extraído demasiado, pero solo necesitamos ver un subconjunto. Podemos cambiar el filtro de Splunk, pero, antes de completar nuestra exploración, es desagradable sobrecargar los filtros. Es mucho más fácil filtrar en Python. Una vez que sepamos lo que necesitamos, podemos hacerlo en 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))
Hemos añadido una expresión generadora para filtrar las líneas de origen, que puede manejar un subconjunto significativo.
proyección
en algunos casos, agregamos columnas de datos de origen adicionales que no queremos usar. Por lo tanto, eliminamos estos datos mediante un proyección de cada fila.
en principio, Splunk nunca produce columnas vacías. Sin embargo, los registros de API RESTful pueden causar que los conjuntos de datos contengan una gran cantidad de encabezados de columnas, que son claves proxy basadas en una parte de la URI de solicitud. Estas columnas contendrán una línea de datos de una solicitud que utiliza esa clave proxy. Para otras filas, no hay nada útil en esta columna. Por lo tanto, debemos eliminar estas columnas vacías.
también podemos hacerlo con una expresión generadora, pero se haría un poco larga. La función generadora es más fácil de leer.
def project(reader): for row in reader: yield {k:v for k,v in row.items() if v}
hemos construido una nueva tabla de filas a partir de una parte del lector original. Podemos usarla para envolver la salida de nuestros filtros.
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))
esto reducirá las columnas no utilizadas visibles dentro de la declaración for.
cambio de símbolo
el símbolo row['source'] se volverá más pesado. Usar types.SimpleNamespace es mejor que usar un diccionario. Esto nos permite usar row.source.
esta es una técnica genial para crear algo más útil.
rdr_ns= (types.SimpleNamespace(**row) forrowinreader)
podemos reducirlo a una secuencia de pasos así.
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)))
Por favor, tenga en cuenta el pequeño cambio que hemos hecho en el método format_map(). Desde las propiedades de SimpleNamespace, hemos añadido la función vars() para extraer el diccionario.
podemos usar otras funciones para convertirlo en una función para mantener la simetría sintáctica.
def ns_reader(reader): return (types.SimpleNamespace(**for row in reader)
de hecho, podemos escribirlo como una estructura lambda que se puede usar como una función
ns_reader = lambda reader: (types.SimpleNamespace(**for row in reader)
Aunque el uso de la función ns_reader() y el lambda ns_reader() es el mismo, escribir la documentación y las pruebas unitarias doctest para lambda es un poco más difícil. Por esta razón, debería evitarse la estructura lambda.
Podemos usar map(lambda row: types.SimpleNamespace(** row),reader()). Algunos prefieren esta expresión de generador.
Podemos usar una declaración for adecuada y una declaración yield interna, pero parece que no hay mucho beneficio en escribir declaraciones grandes desde cosas pequeñas.
Tenemos muchas opciones, porque Python ofrece muchas funciones de programación funcional. Aunque no lo vemos a menudo como un lenguaje funcional. Pero tenemos varios métodos para manejar mapeos simples.
Mapeo: conversión y derivación de datos
A menudo tendremos una lista de conversión de datos muy clara. Además, tendremos una lista cada vez más grande de proyectos derivados. Los proyectos derivados serán dinámicos y basados en diferentes hipótesis que estamos probando. Cada vez que tengamos un experimento o problema, podríamos cambiar los datos derivados.
Cada uno de estos pasos: filtrar, proyectar, convertir y derivar son map-La etapa de "map" en el tubo reduce. Podemos crear algunas funciones más pequeñas y aplicárselas a map(). Porque estamos actualizando un objeto con estado, no podemos usar la función map() general. Si queremos implementar un estilo de programación funcional más puro, usaremos un namedtuple inmutable en lugar de un SimpleNamespace mutable.
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
En el proceso de exploración, ajustaremos el cuerpo de esta función de conversión. Tal vez comenzaremos con algunas conversiones y derivaciones más pequeñas. Usaremos algunas preguntas como "¿Estos son correctos?" para continuar explorando. Cuando descubramos que no funciona, los sacaremos.
Nuestro proceso de procesamiento general es como se muestra a continuación:
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)) )
Por favor, tenga en cuenta el cambio en el sujeto de la declaración. La función convert() produce los valores que estamos seguros. Ya hemos agregado algunas variables adicionales en el bucle for, no podemos100% seguro. Antes de actualizar la función convert(), verificaremos si son útiles (incluso correctos).
Reducción
En el aspecto de la reducción, podemos tomar un enfoque de procesamiento ligeramente diferente. Necesitamos refactorizar nuestro ejemplo anterior y convertirlo en una función generadora.
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
Luego reemplazamos un yield por print().
Esto es otra parte de la refactoreación.
for row in converted_log("somefile.csv"): print( "{host:30s} {start_time:%H:%M:%S} {response_time:6.3f} {service}".format_map(vars(row)) )
En el mejor de los casos, todo nuestro código de programación es así. Usamos funciones generadoras para generar datos. La visualización final de los datos se mantiene completamente separada. Esto nos permite ser más libres al refactorizar y cambiar el procesamiento.
Ahora podemos hacer algunas cosas, como recopilar las filas en un objeto Counter() o tal vez calcular algunas estadísticas. Podemos usar defaultdict(list) para agrupar las filas por servicio.
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()) )
Hemos decidido crear objetos de lista específicos aquí. Podemos usar itertools para agrupar el tiempo de respuesta por servicio. Parece ser programación funcional, pero esta implementación en el estilo de programación funcional Pythonic señala algunas limitaciones. O bien debemos ordenar los datos (crear objetos de lista), o crear listas al agrupar datos. Para realizar varias estadísticas, es generalmente más fácil agrupar los datos creando listas específicas.
Estamos haciendo dos cosas ahora, en lugar de simplemente imprimir el objeto de línea.
Creamos algunas variables locales, como svc y m. Podemos agregar fácilmente cambios u otras medidas.
Al usar la función vars() sin parámetros, crea un diccionario a partir de las variables locales.
Este comportamiento de usar vars() sin parámetros es como una técnica conveniente, al igual que locals(). Nos permite crear simplemente cualquier variable local que queramos y incluirlas en la salida formateada. Podemos invadir varias métodos estadísticos que creemos que podrían estar relacionados.
Dado que nuestro bucle de procesamiento básico es para las líneas en converted_log (“somefile.csv”), podemos explorar muchas opciones de procesamiento a través de un pequeño y fácil de modificar script. Podemos explorar algunas hipótesis para determinar por qué algunas API RESTful procesan lentamente, mientras que otras procesan rápidamente.
Resumen
Lo mencionado anteriormente es lo que el editor les ha presentado sobre análisis de datos exploratorios en Python (funcional), espero que les sea útil. Si tienen alguna pregunta, déjenme un mensaje y responderé a tiempo. También agradezco mucho el apoyo a la tutorial de grito!
Declaración: El contenido de este artículo se obtiene de la red, es propiedad del autor original, el contenido se contribuye y carga de manera autónoma por los usuarios de Internet, este sitio no posee los derechos de propiedad, no se ha realizado un procesamiento editorial manual y no asume responsabilidades legales relacionadas. Si encuentra contenido sospechoso de infracción de derechos de autor, por favor envíe un correo electrónico a: notice#oldtoolbag.com (al enviar un correo electrónico, por favor reemplace # con @ para denunciar y proporcionar evidencia. Una vez verificada, este sitio eliminará inmediatamente el contenido sospechoso de infracción de derechos de autor.)