API

Komlogd puede utilizarse como librería en otras aplicaciones, para así integrar en ellas la funcionalidad ofrecida por Komlog.

Nota

komlogd utiliza asyncio internamente, por lo que es necesario que la aplicación donde se integre corra un loop de asyncio para su funcionamiento.

Inicio de sesión en Komlog

Para establecer una sesión con komlog necesitamos:

  • Un usuario de acceso.
  • Una clave RSA.

Nota

Recuerda que komlogd durante su Primera ejecución crea la clave RSA para la comunicación con Komlog. En caso de querer utilizar otra clave RSA, ésta debe ser mayor o igual a 4096 bytes.

Para inicializar una sesión en Komlog bastaría con realizar lo siguiente:

import asyncio
from komlogd.api import session, crypto

loop = asyncio.get_event_loop()

async def example():
    username='username'
    privkey=crypto.load_private_key('/path/to/key.priv')
    komlog_session=session.KomlogSession(username=username, privkey=privkey)
    await komlog_session.login()
    # En este punto la sesión estaría establecida.
    # Si necesitamos que nuestra tarea espere mientras
    # la sesión está establecida:
    # await komlog_session.t_loop

    # Para cerrar la sesión:
    await komlog_session.close()

try:
    loop.run_until_complete(example())
except:
    loop.stop()
loop.close()

Los pasos son:

  • Establecemos el usuario de la sesión.
  • Cargamos la clave privada del agente, llamando a la función crypto.load_private_key() pasándole como parámetro la ruta del archivo que la contiene.
  • Inicializamos un objeto KomlogSession, que será el encargado de contener nuestra sesión con Komlog, pasándole como parámetros el usuario y la clave privada.
  • Llamamos al método login(). Este método crea una tarea en el loop de asyncio que será la encargada de realizar login en el servidor y mantener nuestra sesión con Komlog. Hasta que no se ejecute el loop de asyncio no se realizará login en Komlog.
  • Por último arrancamos el loop de asyncio. En este momento se ejecutará la tarea de login en Komlog.

A partir de ese momento, siempre y cuando el login haya sido correcto, tendríamos una sesión establecida con Komlog, y estaríamos preparados para enviar y recibir información.

Internamente, komlogd establece con el servidor una conexión mediante web sockets, por lo que la sesión se mantiene establecida en todo momento. En caso de recibir una desconexión, la propia librería se encarga de restablecerla.

Importante

Para que el login sea correcto, hay que autorizar previamente la clave pública de komlogd, como se explica en Autorización del agente en Komlog.

La clave pública se puede obtener a partir de la clave privada, ese es el motivo por el cual nunca tienes que introducir la clave pública como parámetro, sino la privada, pero komlogd nunca enviará la clave privada a Komlog.

Ante un inicio de sesión fallido porque la clave pública no ha sido autorizada, komlogd la muestra en los logs, para que el usuario vea cual debe autorizar.

Envío de datos a Komlog

En Komlog el usuario organiza sus métricas en una estructura en árbol en la que cada métrica está identificada unívocamente por lo que llamamos uri. A esta estructura la llamamos el modelo de datos del usuario. A los diferentes valores que puede tomar una métrica a lo largo del tiempo se les conoce como samples.

En el modelo de datos del usuario se pueden crear métricas de los siguientes tipos:

  • Datasource
  • Datapoint

Komlog crea las métricas automáticamente la primera vez que subimos datos de ellas. El tipo de datos de una métrica se establece cuando ésta se crea, y no hay posibilidad de cambiarlo mientras exista. Para modificar el tipo de dato habría que borrar la métrica (lo que implicaría borrar todas las muestras de datos recibidas de ella) y volverla a crear con un tipo diferente.

Métricas de tipo Datasource

Una métrica de tipo Datasource se utiliza para almacenar textos. Podemos subir cualquier texto de una longitud máxima de 130KB.

Para enviar a Komlog un sample de un Datasource se podría hacer de la siguiente manera:

import asyncio
import pandas as pd
from komlogd.api import session, crypto
from komlogd.api.model.orm import Datasource, Sample

loop = asyncio.get_event_loop()

async def send_datasource_sample():
    # establecemos la sessión
    username='username'
    privkey=crypto.load_private_key('/path/to/key.priv')
    komlog_session=session.KomlogSession(username=username, privkey=privkey)
    await komlog_session.login()

    # preparamos el sample
    uri='my_datasource'
    data='Datasource content'
    ts = pd.Timestamp('now',tz='Europe/Madrid')
    metric = Datasource(uri=uri)
    sample = Sample(metric=metric, ts=ts, data=data)

    # enviamos sample y cerramos sessión
    komlog_session.send_samples(samples=[sample])
    await komlog_session.close()

try:
    loop.run_until_complete(send_datasource_sample())
except:
    loop.stop()
finally:
    loop.close()

Métricas de tipo Datapoint

Una métrica de tipo Datapoint se utiliza para almacenar valores numéricos. Se aceptar variables de tipo int, float o Decimal (de estas últimas sólamente las que tienen representación numérica, es decir, no se aceptan valores como infinity, -infinity, NaN, etc).

En el siguiente ejemplo se muestra como enviar un par de samples asociados a dos métricas de tipo Datapoint:

import asyncio
import pandas as pd
from komlogd.api import session, crypto
from komlogd.api.model.orm import Datapoint, Sample

loop = asyncio.get_event_loop()

async def send_datapoint_samples():
    # establecemos la sesión
    username='username'
    privkey=crypto.load_private_key('/path/to/key.priv')
    komlog_session=session.KomlogSession(username=username, privkey=privkey)
    await komlog_session.login()

    # preparamos samples
    samples = []
    ts = pd.Timestamp('now',tz='Europe/Berlin')
    metric1 = Datapoint(uri='cpu.system')
    metric2 = Datapoint(uri='cpu.user')
    samples.append(Sample(metric=metric1, ts=ts, data=14.63))
    samples.append(Sample(metric=metric2, ts=ts, data=28.5))

    # enviamos samples y cerramos sesión
    komlog_session.send_samples(samples=samples)
    await komlog_session.close()

try:
    loop.run_until_complete(send_datapoint_samples())
except:
    loop.stop()
finally:
    loop.close()

Komlog considera las diferentes métricas del modelo de datos del usuario como series temporales independientes, por lo que cuando subimos datos a alguna de nuestras métricas siempre hay que asociarle un timestamp. El usuario es el encargado de establecer el timestamp, por lo que el valor del timestamp no tiene por qué coincidir con el del momento en el que se envían los datos.

El timestamp que asociamos al contenido de una métrica puede ser de los siguientes tipos:

  • tipo pandas.Timestamp
  • tipo datetime.datetime
  • tipo string en formato ISO8601

Hay que tener en cuenta que es necesario incluir la zona horaria y que la precisión máxima aceptada es de milisegundos.

Funciones impulso

En komlogd existe la posibilidad de ejecutar una función cada vez que llegue una muestra de una métrica. A este tipo de funciones les denominamos funciones impulso.

Para crear una función impulso simplemente hay que aplicarle el decorador @impulsemethod. Este decorador admite los siguientes parámetros:

  • uris: lista con las uris de las métricas a las que queremos suscribirnos.
  • data_reqs: objeto de tipo DataRequirements, donde le indicamos los requisitos a nivel de datos que tiene la función para su correcta ejecución.
  • min_exec_delta: objecto tipo pandas.Timedelta. Este parámetro indica el periodo mínimo entre ejecuciones de la función. Por defecto, komlogd ejecutará la función impulso cada vez que se reciban muestras en los métricas suscritas, sin embago, este comportamiento puede no siempre ser el deseado, por lo que este parámetro indica a komlogd que entre ejecución y ejecución al menos debe haber pasado el tiempo especificado.

El siguiente código muestra como se crearía una función impulso:

from komlogd.api.impulse import impulsemethod

@impulsemethod(uris=['cpu.system','cpu.user'])
async def example():
    print('hello komlog.')

En el ejemplo anterior, cada vez que se actualicen las métricas cpu.system y cpu.user komlogd ejecutaría la función example. Como se puede ver, example es una corrutina. El decorador @impulsemethod puede aplicarse tanto a funciones normales o como a corrutinas.

Una función impulso puede provocar la actualización de métricas en nuestro modelo de datos. Para ello debe devolver una lista con los samples que se deben enviar a Komlog:

from komlogd.api.impulse import impulsemethod
from komlogd.api.model.orm import Datasource, Sample

@impulsemethod(uris=['cpu.system','cpu.user'], data_reqs=DataRequirements(past_delta=pd.Timedelta('1h')))
def summary(ts, updated, data, others):
    result={'samples':[]}
    for metric in updated:
        int_data=data[metric][ts-pd.Timedelta('60 min'):ts]
        info=str(int_data.describe())
        stats = Datasource(uri='.'.join((metric.uri,'last_60min_stats')))
        sample = Sample(metric=stats, data=info, ts=ts)
        result['samples'].append(sample)
    return result

En el ejemplo anterior nos suscribimos a las métricas cpu.system y cpu.user y realizamos una serie de cálculos estadísticos sobre sus datos de la última hora. Posteriormente se escriben los resultados en las métricas cpu.system.last_60min_stats y cpu.user.last_60min_stats. La función se ejecutará cada vez que se reciban datos.

A continuación la comentamos en detalle.

  • En primer lugar aplicamos a la función summary el decorador @impulsemethod con los siguientes parámetros:
    • uris=[‘cpu.system’,’cpu.user’]. Con este parámetro indicamos que la función se suscribe a las métricas cpu.system y cpu.user.
    • data_reqs=DataRequirements(past_delta=pd.Timedelta(‘1h’)). Aquí le indicamos que la función necesita 1 hora de datos para su correcta ejecución.
  • La función summary recibe una serie de parámetros (Es opcional que nuestra función reciba estos parámetros. Si no los va a necesitar no hace falta que los definamos):
    • ts: objecto pandas.Timestamp. Es el timestamp de los samples que provocaron la ejecución de la función.
    • updated: Lista de métricas actualizadas en esta ejecución.
    • data: Diccionario que contiene una key por cada una de las métricas suscritas. El valor es un objeto tipo pandas.Series, con los datos de la métrica.
    • others: Lista con métricas a los que está suscrita la función pero que no han provocado la ejecución actual.
  • En la primera línea de la función summary declaramos el diccionario result. Éste contiene la clave samples que será la que almacene los samples a enviar a Komlog.

  • A continuación, por cada una de las métricas que se han actualizado hacemos lo siguiente:
    • Obtenemos los datos de la última hora.
    • Obtenemos el resultado al aplicarles la función describe() (Esta es una función del módulo pandas que obtiene una serie de valores estadísticos sobre una serie).
    • Creamos un Datasource cuya uri será la de la métrica + .last_60min_stats, es decir, crearíamos cpu.system.last_60_min_stats y cpu.user.last_60_min_stats.
    • Creamos un Sample del datasource y establecemos los datos y el timestamp.
    • Añadimos el sample al listado de samples del diccionario result.
  • Por último la función devuelve el diccionario result, con los samples a enviar a Komlog.

Se puede aplicar el decorador impulsemethod a una función tantas veces como se necesite, simplemente apilando las llamadas al mismo. Por ejemplo, en la función anterior, si quisiésemos aplicar la función a dos grupos de métricas diferentes, bastaría con aplicar el decorador a la función una vez por cada grupo de métricas en lugar de crear dos funciones para la misma operación.

@impulsemethod(uris=['host1.cpu.system','host1.cpu.user'], data_reqs=DataRequirements(past_delta=pd.Timedelta('1h')))
@impulsemethod(uris=['host2.cpu.system','host2.cpu.user'], data_reqs=DataRequirements(past_delta=pd.Timedelta('1h')))
def summary(ts, updated, data, others):
    ...

Trabajando con métricas remotas

Komlog permite compartir partes del modelo de datos con otros usuarios.

Nota

Esta funcionalidad está accesible desde el menú de configuración web de Komlog. Hay que tener en cuenta que los datos siempre se comparten en modo de sólo lectura y de forma recursiva, es decir, si comparto la métrica cpu.system estaría compartiendo dicha métrica y todas sus métricas anidadas en el modelo de datos del usuario, sin importar si ya existían o no en el momento de compartirla.

Al compartir las métricas en modo solo lectura, si una función impulso trata de actualizar una métrica remota, dicha actualización fallará. El usuario sólo puede modificar su modelo de datos.

Esta funcionalidad permite la creación de aplicaciones que utilicen modelos de datos distribuidos. La forma para indicar una métrica remota es anteponer el usuario al nombre de la uri:

uri_remota = 'user:uri'

Por ejemplo, si el usuario my_friend nos compartiese la uri host1.cpu, podríamos crear una función impulso que se suscribiese a host1.cpu.system y host1.cpu.user de la siguiente forma:

@impulsemethod(uris=['my_friend:host1.cpu.system','my_friend:host1.cpu.user'], data_reqs=DataRequirements(past_delta=pd.Timedelta('1h')))
def summary(ts, updated, data, others):
    ...

Un impulse method se puede suscribir a métricas propias y remotas a la vez:

uris = [
    'my_friend:host1.cpu.system',
    'my_friend:host1.cpu.user',
    'host1.cpu.system',
    'host1.cpu.user'
]

@impulsemethod(uris=uris, data_reqs=DataRequirements(past_delta=pd.Timedelta('1h')))
def summary(ts, updated, data, others):
    ...