#########################################################################
#
# Copyright (C) 2017 OSGeo
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#########################################################################
import logging
import types
import pytz
from urllib.parse import urlparse
from socket import gethostbyname
from datetime import datetime, timedelta
from decimal import Decimal
from django import forms
from django.db import models
from django.conf import settings
from django.http import Http404
from django.urls import reverse
from django.contrib.auth import get_user_model
from django.db.models.fields.json import JSONField
from django.utils.translation import ugettext_noop as _
try:
from django.contrib.gis.geoip2 import GeoIP2 as GeoIP
except ImportError:
try:
from django.contrib.gis.geoip import GeoIP
except ImportError:
pass
import user_agents
from ipware import get_client_ip
import pycountry
from geonode.monitoring.forms import MultiEmailField
from django.db.models import Sum, F, Case, When, Max
[docs]
log = logging.getLogger(__name__)
[docs]
def get_geoip():
# defer init until it's really needed
# otherwise, some cli commands may fail (like updating geouip)
global GEOIP_DB
if GEOIP_DB is None:
try:
GEOIP_DB = GeoIP()
except Exception as e:
log.exception(e)
return GEOIP_DB
[docs]
class Host(models.Model):
"""
Describes one physical instance
"""
[docs]
name = models.CharField(max_length=255, unique=False, blank=False, null=False)
[docs]
ip = models.GenericIPAddressField(null=False, blank=False)
[docs]
active = models.BooleanField(null=False, blank=False, default=True)
[docs]
def __str__(self):
return f"Host: {self.name} ({self.ip})"
[docs]
class ServiceType(models.Model):
"""
Service Type list
"""
[docs]
TYPE_GEONODE = "geonode"
[docs]
TYPE_GEOSERVER = "geoserver"
[docs]
TYPE_HOST_GN = "hostgeonode"
[docs]
TYPE_HOST_GS = "hostgeoserver"
[docs]
TYPES = (
(
TYPE_GEONODE,
_("GeoNode"),
),
(
TYPE_GEOSERVER,
_("GeoServer"),
),
(
TYPE_HOST_GS,
_(
"Host (GeoServer)",
),
),
(
TYPE_HOST_GN,
_(
"Host (GeoNode)",
),
),
)
[docs]
name = models.CharField(max_length=255, unique=True, blank=False, null=False, choices=TYPES)
[docs]
def __str__(self):
return f"Service Type: {self.name}"
@property
[docs]
def is_system_monitor(self):
return self.name in (
self.TYPE_HOST_GN,
self.TYPE_HOST_GS,
)
[docs]
class Service(models.Model):
"""
Service is a entity describing deployed processes.
"""
[docs]
name = models.CharField(max_length=255, unique=True, blank=False, null=False)
[docs]
host = models.ForeignKey(Host, null=False, on_delete=models.CASCADE)
[docs]
check_interval = models.DurationField(null=False, blank=False, default=timedelta(seconds=60))
[docs]
last_check = models.DateTimeField(null=True, blank=True, auto_now_add=True)
[docs]
service_type = models.ForeignKey(ServiceType, null=False, on_delete=models.CASCADE)
[docs]
active = models.BooleanField(null=False, blank=False, default=True)
[docs]
notes = models.TextField(null=True, blank=True)
[docs]
url = models.URLField(null=True, blank=True, default="")
[docs]
def __str__(self):
return f"Service: {self.name}@{self.host.name}"
[docs]
def get_metrics(self):
return [m.metric for m in self.service_type.metric.all()]
@property
[docs]
def is_hostgeonode(self):
return self.service_type.name == ServiceType.TYPE_HOST_GN
@property
[docs]
def is_hostgeoserver(self):
return self.service_type.name == ServiceType.TYPE_HOST_GS
@property
[docs]
def is_system_monitor(self):
return self.service_type.is_system_monitor
[docs]
class MonitoredResource(models.Model):
[docs]
TYPE_RESOURCE_BASE = "resource_base"
[docs]
TYPE_DOCUMENT = "document"
[docs]
_TYPES = (
TYPE_EMPTY,
TYPE_LAYER,
TYPE_MAP,
TYPE_DOCUMENT,
TYPE_STYLE,
TYPE_ADMIN,
TYPE_URL,
TYPE_OTHER,
)
[docs]
TYPES = (
(
TYPE_EMPTY,
_("No resource"),
),
(
TYPE_LAYER,
_("Dataset"),
),
(
TYPE_MAP,
_("Map"),
),
(
TYPE_RESOURCE_BASE,
_("Resource base"),
),
(
TYPE_DOCUMENT,
_("Document"),
),
(
TYPE_STYLE,
_("Style"),
),
(
TYPE_ADMIN,
_("Admin"),
),
(
TYPE_URL,
_("URL"),
),
(
TYPE_OTHER,
_("Other"),
),
)
[docs]
name = models.CharField(max_length=255, null=False, blank=True, default="")
[docs]
type = models.CharField(max_length=255, null=False, blank=False, choices=TYPES, default=TYPE_EMPTY)
[docs]
resource_id = models.IntegerField(null=True, blank=True)
[docs]
def __str__(self):
return f"Monitored Resource: {self.name} {self.type}"
@classmethod
[docs]
def get(cls, resource_type, resource_name, or_create=False):
if or_create:
res, _ = cls.objects.get_or_create(type=resource_type, name=resource_name)
return res
res = cls.objects.get(type=resource_type, name=resource_name)
[docs]
class Metric(models.Model):
[docs]
TYPE_VALUE_NUMERIC = "value_numeric"
[docs]
TYPES = (
(
TYPE_RATE,
_("Rate"),
),
(
TYPE_COUNT,
_("Count"),
),
(
TYPE_VALUE,
_("Value"),
),
(
TYPE_VALUE_NUMERIC,
_("Value numeric"),
),
)
[docs]
AGGREGATE_MAP = {
TYPE_RATE: (
"(case when sum(samples_count)> 0 " "then sum(value_num*samples_count)" "/sum(samples_count) else 0 end)"
),
TYPE_VALUE: "sum(value_num)",
TYPE_VALUE_NUMERIC: "max(value_num)",
TYPE_COUNT: "sum(value_num)",
}
[docs]
AGGREGATE_DJANGO_MAP = {
TYPE_RATE: (
Sum(
Case(When(samples_count__gt=0, then=F("value_num")), default=0),
output_field=models.DecimalField(max_digits=16, decimal_places=2),
)
/ Sum(
Case(When(samples_count__gt=0, then=F("samples_count")), default=1),
output_field=models.DecimalField(max_digits=16, decimal_places=2),
)
),
TYPE_VALUE: Sum(F("value_num")),
TYPE_COUNT: Sum(F("value_num")),
TYPE_VALUE_NUMERIC: Max(F("value_num")),
}
[docs]
UNITS = (
(
UNIT_BYTES,
_("Bytes"),
),
(
UNIT_KILOBYTES,
_("Kilobytes"),
),
(
UNIT_MEGABYTES,
_("Megabytes"),
),
(
UNIT_GIGABYTES,
_("Gigabytes"),
),
(
UNIT_BPS,
_("Bytes per second"),
),
(
UNIT_KBPS,
_("Kilobytes per second"),
),
(
UNIT_MBPS,
_("Megabytes per second"),
),
(
UNIT_GBPS,
_("Gigabytes per second"),
),
(
UNIT_SECONDS,
_("Seconds"),
),
(
UNIT_RATE,
_("Rate"),
),
(
UNIT_PERCENTAGE,
_("Percentage"),
),
(
UNIT_COUNT,
_("Count"),
),
)
[docs]
name = models.CharField(max_length=255, db_index=True)
[docs]
description = models.CharField(max_length=255, null=True)
[docs]
type = models.CharField(max_length=255, null=False, blank=False, default=TYPE_RATE, choices=TYPES)
[docs]
unit = models.CharField(max_length=255, null=True, blank=True, choices=UNITS)
[docs]
def get_aggregate_field(self):
return self.AGGREGATE_DJANGO_MAP[self.type]
[docs]
def get_aggregate_name(self):
return self.AGGREGATE_MAP[self.type]
[docs]
def __unicode__(self):
return f"Metric: {self.name}"
@property
[docs]
def is_rate(self):
return self.type == self.TYPE_RATE
@property
[docs]
def is_count(self):
return self.type == self.TYPE_COUNT
@property
[docs]
def is_value_numeric(self):
return self.type == self.TYPE_VALUE_NUMERIC
@property
[docs]
def is_value(self):
return self.type == self.TYPE_VALUE
@classmethod
[docs]
def get_for(cls, name, service=None):
metric = None
if service:
try:
stype = ServiceTypeMetric.objects.get(service_type=service.service_type, metric__name=name)
metric = stype.metric
except ServiceTypeMetric.DoesNotExist:
raise Http404()
else:
metric = Metric.objects.filter(name=name).first()
return metric
[docs]
class ServiceTypeMetric(models.Model):
[docs]
service_type = models.ForeignKey(ServiceType, related_name="metric", on_delete=models.CASCADE)
[docs]
metric = models.ForeignKey(Metric, related_name="service_type", on_delete=models.CASCADE)
[docs]
def __str__(self):
return f"{self.service_type} - {self.metric}"
[docs]
class EventType(models.Model):
[docs]
_ows_types = "tms wms-c wmts wcs wfs wms wps".upper().split(" ")
# OWS_OTHER = 'other'
# OWS_ALL = 'all'
# OWS_TYPES = zip(_ows_types, _ows_types) + \
# [(OWS_ALL, _("All"))] + [(OWS_OTHER, _("Other"))]
[docs]
EVENT_DOWNLOAD = "download"
[docs]
EVENT_CREATE = "create"
[docs]
EVENT_CHANGE = "change"
[docs]
EVENT_REMOVE = "remove"
[docs]
EVENT_PUBLISH = "publish"
[docs]
EVENT_UPLOAD = "upload"
[docs]
EVENT_GEOSERVER = "geoserver" # other event from GS
# special event types
[docs]
EVENT_OWS = "OWS:ALL" # any ows event
[docs]
EVENT_OTHER = "other" # non-ows event
[docs]
EVENT_ALL = "all" # all events - baseline: ows + non-ows
[docs]
EVENT_TYPES = (
list(zip([f"OWS:{ows}" for ows in _ows_types], _ows_types))
+ [(EVENT_OTHER, _("Not OWS"))]
+ [(EVENT_OWS, _("Any OWS"))]
+ [(EVENT_ALL, _("All"))]
+ [(EVENT_CREATE, _("Create"))]
+ [(EVENT_UPLOAD, _("Upload"))]
+ [(EVENT_CHANGE, _("Change"))]
+ [(EVENT_CHANGE_METADATA, _("Change Metadata"))]
+ [(EVENT_VIEW_METADATA, _("View Metadata"))]
+ [(EVENT_VIEW, _("View"))]
+ [(EVENT_DOWNLOAD, _("Download"))]
+ [(EVENT_PUBLISH, _("Publish"))]
+ [(EVENT_REMOVE, _("Remove"))]
+ [(EVENT_GEOSERVER, _("Geoserver event"))]
)
[docs]
name = models.CharField(max_length=16, unique=True, choices=EVENT_TYPES, null=False, blank=False)
[docs]
def __str__(self):
return f"Event Type: {self.name}"
@classmethod
[docs]
def get(cls, service_name=None):
if not service_name:
return
try:
q = models.Q(name=service_name)
try:
s = int(service_name)
except (
ValueError,
TypeError,
):
s = None
if s:
q = q | models.Q(id=s)
return cls.objects.get(q)
except cls.DoesNotExist:
return
@property
[docs]
def is_all(self):
return self.name == self.EVENT_ALL
@property
[docs]
def is_other(self):
return self.name == self.EVENT_OTHER
@property
[docs]
def is_ows(self):
return self.name.upper().startswith("OWS:")
@property
[docs]
def is_download(self):
return self.name == self.EVENT_DOWNLOAD
@property
[docs]
def is_view(self):
return self.name == self.EVENT_VIEW
[docs]
class RequestEvent(models.Model):
[docs]
_methods = "get post head options put delete".upper().split(" ")
[docs]
METHODS = list(zip(_methods, _methods))
[docs]
created = models.DateTimeField(db_index=True, null=False)
[docs]
received = models.DateTimeField(db_index=True, null=False)
[docs]
service = models.ForeignKey(Service, on_delete=models.CASCADE)
[docs]
event_type = models.ForeignKey(EventType, blank=True, null=True, on_delete=models.CASCADE)
[docs]
host = models.CharField(max_length=255, blank=True, default="")
[docs]
request_path = models.TextField(blank=False, default="")
# resources is a list of affected resources. it is buld as a pair of type and name:
# layer=geonode:sample_dataset01
# or
# document=documents/id
# or
# map=some map
#
# list is separated with newline
# resources = models.TextField(blank=True, default='',
# help_text=_("Resources name (style, layer, document, map)"))
[docs]
resources = models.ManyToManyField(
MonitoredResource, blank=True, help_text=_("List of resources affected"), related_name="requests"
)
[docs]
request_method = models.CharField(max_length=16, choices=METHODS)
[docs]
response_status = models.PositiveIntegerField(null=False, blank=False)
[docs]
response_size = models.PositiveIntegerField(null=False, default=0)
[docs]
response_time = models.PositiveIntegerField(null=False, default=0, help_text=_("Response processing time in ms"))
[docs]
response_type = models.CharField(max_length=255, null=True, blank=True, default="")
[docs]
user_agent = models.CharField(max_length=255, null=True, blank=True, default=None)
[docs]
user_agent_family = models.CharField(max_length=255, null=True, default=None, blank=True)
[docs]
client_ip = models.GenericIPAddressField(null=True, blank=True)
[docs]
client_lat = models.DecimalField(max_digits=11, decimal_places=5, null=True, default=None, blank=True)
[docs]
client_lon = models.DecimalField(max_digits=11, decimal_places=5, null=True, default=None, blank=True)
[docs]
client_country = models.CharField(max_length=255, null=True, default=None, blank=True)
[docs]
client_region = models.CharField(max_length=255, null=True, default=None, blank=True)
[docs]
client_city = models.CharField(max_length=255, null=True, default=None, blank=True)
[docs]
custom_id = models.CharField(max_length=255, null=True, default=None, blank=True, db_index=True)
# keep user anonymized identifier
[docs]
user_identifier = models.CharField(max_length=255, null=True, default=None, blank=True, db_index=True)
[docs]
user_username = models.CharField(max_length=150, default=None, null=True, blank=True)
@classmethod
[docs]
def _get_resources(cls, type_name, resources_list):
out = []
for r in resources_list:
if r is None:
continue
rinst, _ = MonitoredResource.objects.get_or_create(name=r, type=type_name)
out.append(rinst)
return out
@classmethod
[docs]
def _get_or_create_resources(cls, res_name, res_type, res_id):
out = []
r, _ = MonitoredResource.objects.get_or_create(name=res_name, type=res_type)
if r and res_id:
r.resource_id = res_id
r.save()
out.append(r)
return out
@classmethod
[docs]
def _get_geonode_resources(cls, request):
"""
Return serialized resources affected by request
"""
rqmeta = getattr(request, "_monitoring", {})
events = rqmeta["events"]
resources = []
# for type_name in 'layer map document style'.split():
# res = rqmeta['resources'].get(type_name) or []
# resources.extend(cls._get_resources(type_name, res))
for evt_type, res_type, res_name, res_id in events:
resources.extend(cls._get_or_create_resources(res_name, res_type, res_id))
return resources
@classmethod
[docs]
def _get_event_type(cls, request, default_event_type="view"):
"""
Returns event type based on events
"""
rqmeta = getattr(request, "_monitoring", {})
events = {e[0] for e in rqmeta["events"]}
event_name = default_event_type
if len(events) == 1:
event_name = events.pop()
elif len(events) == 2 and default_event_type in events:
events.remove(default_event_type)
event_name = events.pop()
return EventType.get(event_name)
@staticmethod
[docs]
def _get_ua_family(ua):
return str(user_agents.parse(ua))
@classmethod
[docs]
def _get_user_agent(cls, ua):
ua_family = cls._get_ua_family(ua)
return {"user_agent": ua, "user_agent_family": ua_family}
@classmethod
[docs]
def _get_user_consent(cls, request):
return settings.USER_ANALYTICS_ENABLED
# if request.user.is_authenticated:
# return request.user.allow_analytics
# return True
@classmethod
[docs]
def _get_user_location(cls, request_ip):
out = {}
lat = lon = None
country = region = city = None
if request_ip:
geoip = get_geoip()
if request_ip in ("127.0.0.1",):
return out
try:
client_loc = geoip.city(request_ip)
except Exception as err:
log.warning("Cannot resolve %s: %s", request_ip, err)
client_loc = None
if client_loc:
lat, lon = (
client_loc["latitude"],
client_loc["longitude"],
)
country = client_loc.get("country_code3") or client_loc["country_code"]
if len(country) == 2:
_c = pycountry.countries.get(alpha_2=country)
country = _c.alpha_3
region = client_loc["region"]
city = client_loc["city"]
out.update(
{
"client_ip": request_ip,
"client_lat": lat,
"client_lon": lon,
"client_country": country,
"client_region": region,
"client_city": city,
}
)
return out
@classmethod
[docs]
def _get_user_data_gn(cls, request):
out = {}
# check consent
if not cls._get_user_consent(request):
return out
rqmeta = getattr(request, "_monitoring", {})
if rqmeta.get("user_identifier"):
out["user_identifier"] = rqmeta.get("user_identifier")
if rqmeta.get("user_username"):
out["user_username"] = rqmeta.get("user_username")
ua = request.META.get("HTTP_USER_AGENT") or ""
ua_data = cls._get_user_agent(ua)
out.update(ua_data)
request_ip, is_routable = get_client_ip(request)
if request_ip and is_routable:
location_data = cls._get_user_location(request_ip)
out.update(location_data)
return out
@classmethod
[docs]
def _get_user_data_gs(cls, request):
out = {}
# check consent
# if not cls._get_user_consent(request):
# return out
ua = request.get("remoteUserAgent") or ""
ua_data = cls._get_user_agent(ua)
out.update(ua_data)
request_ip = request.get("remoteAddr")
if request_ip:
location_data = cls._get_user_location(request_ip)
out.update(location_data)
return out
@classmethod
[docs]
def from_geonode(cls, service, request, response):
from geonode.utils import parse_datetime
received = datetime.utcnow().replace(tzinfo=pytz.utc)
rqmeta = getattr(request, "_monitoring", {})
created = rqmeta.get("started", received)
if not isinstance(created, datetime):
created = parse_datetime(created)
_ended = rqmeta.get("finished", datetime.utcnow().replace(tzinfo=pytz.utc))
duration = (_ended - created).microseconds / 1000.0
sensitive_data = cls._get_user_data_gn(request)
event_type = cls._get_event_type(request)
data = {
"received": received,
"created": created,
"host": request.get_host(),
"service": service,
"user_identifier": None,
"user_username": None,
"event_type": event_type,
"request_path": request.get_full_path(),
"request_method": request.method,
"response_status": response.status_code,
"response_size": response.get("Content-length") or len(response.getvalue()),
"response_type": response.get("Content-type"),
"response_time": duration,
}
data.update(sensitive_data)
try:
inst = cls.objects.create(**data)
resources = cls._get_geonode_resources(request)
if resources:
inst.resources.add(*resources)
inst.save()
return inst
except Exception:
return None
@classmethod
[docs]
def from_geoserver(cls, service, request_data, received=None):
"""
Writes RequestEvent for data from audit log in GS
"""
from dateutil.tz import tzlocal
from geonode.utils import parse_datetime
rd = request_data.get("org.geoserver.monitor.RequestData")
if not rd:
log.warning("No request data payload in %s", request_data)
return
if not rd.get("status") in (
"FINISHED",
"FAILED",
):
log.warning("request not finished %s", rd.get("status"))
return
received = received or datetime.utcnow().replace(tzinfo=pytz.utc)
sensitive_data = cls._get_user_data_gs(rd)
# ua = rd.get('remoteUserAgent') or ''
# ua_family = cls._get_ua_family(ua)
# ip = rd['remoteAddr']
# lat = lon = None
# country = region = city = None
# if ip:
# geoip = get_geoip()
# try:
# client_loc = geoip.city(ip)
# except Exception as err:
# log.warning("Cannot resolve %s: %s", ip, err)
# client_loc = None
#
# if client_loc:
# lat, lon = client_loc['latitude'], client_loc['longitude'],
# country = client_loc.get(
# 'country_code3') or client_loc['country_code']
# if len(country) == 2:
# _c = pycountry.countries.get(alpha_2=country)
# country = _c.alpha_3
# region = client_loc['region']
# city = client_loc['city']
utc = pytz.utc
try:
local_tz = pytz.timezone(datetime.now(tzlocal()).tzname())
except Exception:
local_tz = pytz.timezone(settings.TIME_ZONE)
start_time = parse_datetime(rd["startTime"])
# Assuming GeoServer stores dates @ UTC
start_time = start_time.replace(tzinfo=utc).astimezone(local_tz)
rl = rd["responseLength"]
event_type_name = rd.get("service")
if event_type_name:
event_type = EventType.get(f"OWS:{event_type_name.upper()}")
else:
event_type = EventType.get(EventType.EVENT_GEOSERVER)
if rd.get("queryString"):
request_path = f"{rd['path']}?{rd['queryString']}"
else:
request_path = rd["path"]
data = {
"created": start_time,
"received": received,
"host": rd["host"],
"event_type": event_type,
"service": service,
"request_path": request_path,
"request_method": rd["httpMethod"],
"response_status": rd["responseStatus"],
"response_size": rl[0] if isinstance(rl, list) else rl,
"response_type": rd.get("responseContentType"),
"response_time": rd["totalTime"],
}
data.update(sensitive_data)
inst = cls.objects.create(**data)
resource_names = (rd.get("resources") or {}).get("string") or []
if not isinstance(resource_names, (list, tuple)):
resource_names = [resource_names]
resources = cls._get_resources("layer", resource_names)
if rd.get("error"):
try:
etype = rd["error"]["@class"] if "@class" in rd["error"] else rd["error"]["class"]
edata = "\n".join(rd["error"]["stackTrace"]["trace"])
emessage = rd["error"]["detailMessage"] if "detailMessage" in rd["error"] else str(rd["error"])
ExceptionEvent.add_error(service, etype, edata, message=emessage, request=inst)
except Exception:
emessage = rd["error"]["detailMessage"] if "detailMessage" in rd["error"] else str(rd["error"])
ExceptionEvent.add_error(
service, "undefined", "\n".join(rd["error"]["stackTrace"]["trace"]), message=emessage, request=inst
)
if resources:
inst.resources.add(*resources)
inst.save()
return inst
[docs]
class ExceptionEvent(models.Model):
[docs]
created = models.DateTimeField(db_index=True, null=False)
[docs]
received = models.DateTimeField(db_index=True, null=False)
[docs]
service = models.ForeignKey(Service, on_delete=models.CASCADE)
[docs]
error_type = models.CharField(max_length=255, null=False, db_index=True)
[docs]
error_message = models.TextField(null=False, default="")
[docs]
error_data = models.TextField(null=False, default="")
[docs]
request = models.ForeignKey(RequestEvent, related_name="exceptions", on_delete=models.CASCADE)
@classmethod
[docs]
def add_error(cls, from_service, error_type, stack_trace, request=None, created=None, message=None):
received = datetime.utcnow().replace(tzinfo=pytz.utc)
if not isinstance(error_type, str):
_cls = error_type.__class__
error_type = f"{_cls.__module__}.{_cls.__name__}"
if not message:
message = str(error_type)
if isinstance(stack_trace, (list, tuple)):
stack_trace = "".join(stack_trace)
if not isinstance(created, datetime):
created = received
return cls.objects.create(
created=created,
received=received,
service=from_service,
error_type=error_type,
error_data=stack_trace,
error_message=message or "",
request=request,
)
@property
[docs]
def url(self):
return reverse("monitoring:api_exception", args=(self.id,))
@property
[docs]
def service_data(self):
return {"name": self.service.name, "type": self.service.service_type.name}
[docs]
def expose(self):
e = self
data = {
"error_type": e.error_type,
"error_data": e.error_data,
"error_message": e.error_message,
"created": e.created,
"service": {"name": e.service.name, "type": e.service.service_type.name},
"request": {
"request": {
"created": e.request.created,
"method": e.request.request_method,
"path": e.request.request_path,
"host": e.request.host,
},
"event_type": e.request.event_type.name if e.request.event_type else None,
"resources": [{"name": str(r)} for r in e.request.resources.all()],
"client": {
"ip": e.request.client_ip,
"user_agent": e.request.user_agent,
"user_agent_family": e.request.user_agent_family,
"position": {
"lat": e.request.client_lat,
"lon": e.request.client_lon,
"country": e.request.client_country,
"city": e.request.client_city,
},
},
"response": {
"size": e.request.response_size,
"status": e.request.response_status,
"time": e.request.response_time,
"type": e.request.response_type,
},
},
}
return data
[docs]
class MetricLabel(models.Model):
[docs]
name = models.TextField(null=False, blank=True, default="")
[docs]
user = models.CharField(max_length=150, default=None, null=True, blank=True)
[docs]
def __unicode__(self):
return f"Metric Label: {self.name.encode('ascii', 'ignore')}"
[docs]
class MetricValue(models.Model):
[docs]
valid_from = models.DateTimeField(db_index=True, null=False)
[docs]
valid_to = models.DateTimeField(db_index=True, null=False)
[docs]
service_metric = models.ForeignKey(ServiceTypeMetric, on_delete=models.CASCADE)
[docs]
service = models.ForeignKey(Service, on_delete=models.CASCADE)
[docs]
event_type = models.ForeignKey(
EventType, null=True, blank=True, on_delete=models.CASCADE, related_name="metric_values"
)
[docs]
resource = models.ForeignKey(
MonitoredResource, null=True, blank=True, on_delete=models.CASCADE, related_name="metric_values"
)
[docs]
label = models.ForeignKey(MetricLabel, related_name="metric_values", on_delete=models.CASCADE)
[docs]
value = models.CharField(max_length=255, null=False, blank=False)
[docs]
value_num = models.DecimalField(max_digits=20, decimal_places=4, null=True, default=None, blank=True)
[docs]
value_raw = models.TextField(null=True, default=None, blank=True)
[docs]
samples_count = models.PositiveIntegerField(null=False, default=0, blank=False)
[docs]
data = JSONField(null=False, default=dict)
[docs]
def __str__(self):
metric = self.service_metric.metric.name
if self.label:
_l = self.label.name
metric = f"{metric} [{_l}]"
if self.resource and self.resource.type:
_s = f"{self.resource.type}={self.resource.name}"
metric = f"{metric} for {_s}"
return f"Metric Value: {metric}: [{self.value}] (since {self.valid_from} until {self.valid_to})"
@classmethod
[docs]
def add(
cls,
metric,
valid_from,
valid_to,
service,
label,
value_raw=None,
resource=None,
value=None,
value_num=None,
data=None,
event_type=None,
samples_count=None,
):
"""
Create new MetricValue shortcut
"""
if isinstance(metric, Metric):
service_metric = ServiceTypeMetric.objects.get(service_type=service.service_type, metric=metric)
else:
service_metric = ServiceTypeMetric.objects.get(service_type=service.service_type, metric__name=metric)
label_name = label
label_user = None
if label and isinstance(label, tuple):
label_name = label[0]
label_user = label[1]
try:
label, c = MetricLabel.objects.get_or_create(name=label_name or "count")
except MetricLabel.MultipleObjectsReturned:
c = False
label = MetricLabel.objects.filter(name=label_name).first()
if c and label_user:
label.user = label_user
label.save()
if event_type:
if not isinstance(event_type, EventType):
event_type = EventType.get(event_type)
try:
inst = cls.objects.filter(
valid_from=valid_from,
valid_to=valid_to,
service=service,
label=label,
resource=resource,
event_type=event_type,
service_metric=service_metric,
).last()
if inst:
inst.value = abs(value) if value else 0
inst.value_raw = abs(value_raw) if value_raw else 0
inst.value_num = abs(value_num) if value_num else 0
inst.samples_count = samples_count or 0
inst.save()
return inst
except cls.DoesNotExist:
pass
return cls.objects.create(
valid_from=valid_from,
valid_to=valid_to,
service=service,
service_metric=service_metric,
label=label,
resource=resource,
event_type=event_type,
value=value_raw,
value_raw=value_raw,
value_num=value_num,
samples_count=samples_count or 0,
data=data or {},
)
@classmethod
[docs]
def get_for(cls, metric, service=None, valid_on=None, resource=None, label=None, event_type=None):
qparams = models.Q()
if isinstance(metric, Metric):
qparams = qparams & models.Q(service_metric__metric=metric)
else:
qparams = qparams & models.Q(service_metric__metric__name=metric)
if service:
if isinstance(service, Service):
qparams = qparams & models.Q(service_metric__service_type=service.service_type)
elif isinstance(service, ServiceType):
qparams = qparams & models.Q(service_metric__service_type=service)
else:
qparams = qparams & models.Q(service_metric__service_type__name=service)
if valid_on:
qwhen = models.Q(valid_from__lte=valid_on) & models.Q(valid_to__gte=valid_on)
qparams = qparams & qwhen
if label:
if isinstance(label, MetricLabel):
qparams = qparams & models.Q(label=label)
else:
qparams = qparams & models.Q(label__name=label)
if resource:
if isinstance(resource, MonitoredResource):
qparams = qparams & models.Q(resource=resource)
else:
rtype, rname = resource.split("=")
qparams = qparams & models.Q(resource__type=rtype, resource__name=rname)
if event_type:
if isinstance(event_type, EventType):
qparams = qparams & models.Q(event_type=event_type)
else:
qparams = qparams & models.Q(event_type__name=event_type)
q = cls.objects.filter(qparams).order_by("-valid_to")
return q
[docs]
class NotificationCheck(models.Model):
[docs]
GRACE_PERIOD_1M = timedelta(seconds=60)
[docs]
GRACE_PERIOD_5M = timedelta(seconds=5 * 60)
[docs]
GRACE_PERIOD_10M = timedelta(seconds=10 * 60)
[docs]
GRACE_PERIOD_30M = timedelta(seconds=30 * 60)
[docs]
GRACE_PERIOD_1H = timedelta(seconds=60 * 60)
[docs]
GRACE_PERIODS = (
(
GRACE_PERIOD_1M,
_("1 minute"),
),
(
GRACE_PERIOD_5M,
_("5 minutes"),
),
(
GRACE_PERIOD_10M,
_("10 minutes"),
),
(
GRACE_PERIOD_30M,
_("30 minutes"),
),
(
GRACE_PERIOD_1H,
_("1 hour"),
),
)
[docs]
SEVERITY_WARNING = "warning"
[docs]
SEVERITY_ERROR = "error"
[docs]
SEVERITY_FATAL = "fatal"
[docs]
SEVERITIES = (
(
SEVERITY_WARNING,
_("Warning"),
),
(
SEVERITY_ERROR,
_("Error"),
),
(
SEVERITY_FATAL,
_("Fatal"),
),
)
[docs]
name = models.CharField(max_length=255, null=False, blank=False, unique=True)
[docs]
description = models.CharField(max_length=255, null=False, blank=False, help_text="Description of the alert")
[docs]
user_threshold = JSONField(
default=dict, null=False, blank=False, help_text=_("Expected min/max values for user configuration")
)
[docs]
metrics = models.ManyToManyField(Metric, through="NotificationMetricDefinition", related_name="+")
[docs]
last_send = models.DateTimeField(null=True, blank=True, help_text=_("Marker of last delivery"))
[docs]
grace_period = models.DurationField(
null=False,
default=GRACE_PERIOD_10M,
choices=GRACE_PERIODS,
help_text=_("Minimum time between subsequent notifications"),
)
[docs]
severity = models.CharField(
max_length=32,
null=False,
default=SEVERITY_ERROR,
choices=SEVERITIES,
help_text=_("How severe would be error from this notification"),
)
[docs]
active = models.BooleanField(default=True, null=False, blank=False, help_text=_("Is it active"))
[docs]
def __str__(self):
return f"Notification Check #{self.id}: {self.name}"
@property
[docs]
def notification_subject(self):
return _(f"{self.severity}: {self.name}")
@property
[docs]
def is_warning(self):
return self.severity == self.SEVERITY_WARNING
@property
[docs]
def is_error(self):
return self.severity == self.SEVERITY_ERROR
@property
[docs]
def is_fatal(self):
return self.severity == self.SEVERITY_FATAL
@property
[docs]
def can_send(self):
if self.last_send is None:
return True
now = datetime.utcnow().replace(tzinfo=pytz.utc)
self.last_send = self.last_send.replace(tzinfo=pytz.utc)
if (self.last_send + self.grace_period) > now:
return False
return True
[docs]
def mark_send(self):
self.last_send = datetime.utcnow().replace(tzinfo=pytz.utc)
self.save()
@property
[docs]
def url(self):
return reverse("monitoring:api_user_notification_config", args=(self.id,))
[docs]
def get_users(self):
return [r.user for r in self.receivers.exclude(user__isnull=True).select_related("user")]
[docs]
def get_emails(self):
return [r.email for r in self.receivers.exclude(email__isnull=True)]
@property
[docs]
def emails(self):
return [u for u in self.get_emails() if u] + [u.email for u in self.get_users() if u.email]
[docs]
def check_notifications(self, for_timestamp=None):
checks = []
for ch in self.checks.all():
try:
ch.check_metric(for_timestamp=for_timestamp)
except MetricNotificationCheck.MetricValueError as err:
checks.append(err)
# no value available, ignoring
except ValueError:
pass
return checks
@classmethod
[docs]
def check_for(cls, for_timestamp=None, active=None):
checked = []
q = {}
if active is None:
q["active"] = True
elif active is not None:
q["active"] = active
for n in cls.objects.filter(**q):
checked.append(
(
n,
n.check_notifications(for_timestamp=for_timestamp),
)
)
return checked
@classmethod
[docs]
def get_steps(cls, min_, max_, thresholds):
if isinstance(thresholds, (int, float, Decimal)):
if min_ is None or max_ is None:
raise ValueError("Cannot use numeric threshold if one of min/max is None")
step = (max_ - min_) / thresholds
current = min_
thresholds = []
while current < max_:
thresholds.append(current)
current += step
if isinstance(thresholds, (tuple, types.GeneratorType)):
thresholds = list(thresholds)
elif isinstance(thresholds, list) or thresholds is None:
pass
else:
raise TypeError(f"Unsupported threshold type: {thresholds} ({type(thresholds)})")
return thresholds
@classmethod
[docs]
def create(cls, name, description, user_threshold, severity=None):
inst, _ = cls.objects.get_or_create(name=name)
inst.description = description
user_thresholds = {}
for (
metric_name,
field_opt,
use_service,
use_resource,
use_label,
use_event_type,
minimum,
maximum,
thresholds,
_description,
) in user_threshold:
# metric_name is a string for metric.name
# field opt is NotificationMetricDefinition.FIELD_OPTION* value
# use_* are flags to set limitations on scope of alert
# minimum, maximum are min/max for allowed values
# if one is None, that means there's no value limit in that direction
# thresholds can be :
# * list of values between min and max or
# * one number of steps between min and max
# * None, then user can enter value manually
#
# example:
# notfication: system overload
# ('request.count', 'min_value', True, True, True, True,
# 0, None, (100, 200, 500, 1000,)
metric, _ = Metric.objects.get_or_create(name=metric_name)
steps = cls.get_steps(minimum, maximum, thresholds)
nm, _ = NotificationMetricDefinition.objects.get_or_create(
notification_check=inst,
metric=metric,
description=_description,
min_value=minimum,
max_value=maximum,
steps=len(steps) if steps else None,
field_option=field_opt,
)
user_thresholds[nm.field_name] = {
"min": minimum,
"max": maximum,
"metric": metric_name,
"description": _description,
"steps": steps,
}
inst.user_threshold = user_thresholds
if severity is not None:
inst.severity = severity
inst.save()
return inst
[docs]
def get_user_threshold(self, notification_def):
return self.user_threshold[notification_def.field_name]
[docs]
def get_definition_for(self, def_name):
_v = def_name.split(".")
# syntax of field name:
# field_id.metric.name.field_name
mname, field = ".".join(_v[:-1]), _v[-1]
return self.definitions.get(metric__name=mname, field_option=field)
[docs]
class NotificationReceiver(models.Model):
[docs]
notification_check = models.ForeignKey(NotificationCheck, related_name="receivers", on_delete=models.CASCADE)
[docs]
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.CASCADE)
[docs]
email = models.EmailField(null=True, blank=True)
[docs]
def save(self, *args, **kwargs):
if not (self.user or self.email):
raise ValueError("Cannot save empty notification receiver")
super().save(*args, **kwargs)
[docs]
class NotificationMetricDefinition(models.Model):
[docs]
FIELD_OPTION_MIN_VALUE = "min_value"
[docs]
FIELD_OPTION_MAX_VALUE = "max_value"
[docs]
FIELD_OPTION_MAX_TIMEOUT = "max_timeout"
[docs]
FIELD_OPTION_CHOICES = (
(
FIELD_OPTION_MIN_VALUE,
_("Value must be above"),
),
(
FIELD_OPTION_MAX_VALUE,
_("Value must be below"),
),
(
FIELD_OPTION_MAX_TIMEOUT,
_("Last update must not be older than"),
),
)
[docs]
notification_check = models.ForeignKey(NotificationCheck, related_name="definitions", on_delete=models.CASCADE)
[docs]
metric = models.ForeignKey(Metric, related_name="notification_checks", on_delete=models.CASCADE)
[docs]
use_service = models.BooleanField(null=False, default=False)
[docs]
use_resource = models.BooleanField(null=False, default=False)
[docs]
use_label = models.BooleanField(null=False, default=False)
[docs]
use_event_type = models.BooleanField(null=False, default=False)
[docs]
field_option = models.CharField(
max_length=32, choices=FIELD_OPTION_CHOICES, null=False, default=FIELD_OPTION_MIN_VALUE
)
[docs]
description = models.TextField(null=True)
[docs]
min_value = models.DecimalField(max_digits=20, decimal_places=4, null=True, default=None, blank=True)
[docs]
max_value = models.DecimalField(max_digits=20, decimal_places=4, null=True, default=None, blank=True)
[docs]
steps = models.PositiveIntegerField(null=True, blank=True, default=None)
@property
[docs]
def unit(self):
return self.metric.unit if not self.metric.is_count else ""
[docs]
def is_min_val(self):
return self.field_option == self.FIELD_OPTION_MIN_VALUE
[docs]
def is_max_val(self):
return self.field_option == self.FIELD_OPTION_MAX_VALUE
[docs]
def is_max_timeout(self):
return self.field_option == self.FIELD_OPTION_MAX_TIMEOUT
@property
[docs]
def steps_calculated(self):
min_, max_, steps = self.min_value, self.max_value, self.steps
format = "{0:.3g}"
if steps is not None and min_ is not None and max_ is not None:
return [format.format(v) for v in NotificationCheck.get_steps(min_, max_, steps)]
@property
[docs]
def is_enabled(self):
return self.current_value is not None
@property
[docs]
def current_value(self):
try:
m = self.metric_check
if not m:
return
val = getattr(m, self.field_option)
if isinstance(val, timedelta):
val_ = val.total_seconds()
else:
val_ = val
return {"class": f"{val.__class__.__module__}.{val.__class__.__name__}", "value": val_}
except MetricNotificationCheck.DoesNotExist:
return
[docs]
def get_fields(self):
out = []
fid_base = self.field_name
min_, max_, steps = self.min_value, self.max_value, self.steps_calculated
if steps is not None and min_ is not None and max_ is not None:
field = forms.ChoiceField(
choices=[
(
v,
v,
)
for v in steps
],
required=False,
)
else:
fargs = {}
if max_ is not None:
fargs["max_value"] = max_
if min_ is not None:
fargs["min_value"] = min_
field = forms.DecimalField(max_digits=20, decimal_places=4, required=False, **fargs)
field.name = fid_base
out.append(field)
return out
@property
[docs]
def field_name(self):
return f"{self.metric.name}.{self.field_option}"
[docs]
def populate_min_max(self):
notification = self.notification_check
uthreshold = notification.get_user_threshold(self)
self.min_value = uthreshold["min"]
self.max_value = uthreshold["max"]
self.steps = uthreshold["steps"]
try:
self.metric_check
except MetricNotificationCheck.DoesNotExist:
try:
mcheck = MetricNotificationCheck.objects.filter(
notification_check=self.notification_check,
metric=self.metric,
**{f"{self.field_option}__isnull": False},
).get()
if mcheck:
self.metric_check = mcheck
except MetricNotificationCheck.DoesNotExist:
pass
self.save()
[docs]
class MetricNotificationCheck(models.Model):
[docs]
notification_check = models.ForeignKey(NotificationCheck, related_name="checks", on_delete=models.CASCADE)
[docs]
metric = models.ForeignKey(Metric, related_name="checks", on_delete=models.CASCADE)
[docs]
service = models.ForeignKey(Service, related_name="checks", null=True, blank=True, on_delete=models.CASCADE)
[docs]
resource = models.ForeignKey(MonitoredResource, null=True, blank=True, on_delete=models.CASCADE)
[docs]
label = models.ForeignKey(MetricLabel, null=True, blank=True, on_delete=models.CASCADE)
[docs]
event_type = models.ForeignKey(EventType, null=True, blank=True, on_delete=models.CASCADE)
[docs]
min_value = models.DecimalField(max_digits=20, decimal_places=4, null=True, default=None, blank=True)
[docs]
max_value = models.DecimalField(max_digits=20, decimal_places=4, null=True, default=None, blank=True)
[docs]
max_timeout = models.DurationField(
null=True, blank=True, help_text=_("Max timeout for given metric before error should be raised")
)
[docs]
active = models.BooleanField(default=True, null=False, blank=False)
[docs]
definition = models.OneToOneField(
NotificationMetricDefinition, null=True, related_name="metric_check", on_delete=models.CASCADE
)
[docs]
def __str__(self):
indicator = []
if self.min_value is not None:
indicator.append(f"value above {self.min_value}")
if self.max_value is not None:
indicator.append(f"value below {self.max_value}")
if self.max_timeout is not None:
indicator.append(f"value must be collected within {self.max_timeout}")
indicator = " and ".join(indicator)
return f"MetricCheck({self.metric.name}@{(self.service.name if self.service else '')}: {indicator})"
@property
[docs]
def field_option(self):
field_option = None
if self.min_value:
field_option = "min_value"
elif self.max_value:
field_option = "max_value"
elif self.max_timeout:
field_option = "max_timeout"
if field_option is None:
raise ValueError("Cannot establish field_option value")
return field_option
[docs]
class MetricValueError(ValueError):
def __init__(self, metric, check, message, offending_value, threshold_value, description):
[docs]
self.name = metric.service_metric.metric.name if hasattr(metric, "service_metric") else str(metric)
[docs]
self.offending_value = offending_value
[docs]
self.threshold_value = threshold_value
[docs]
self.severity = check.notification_check.severity if hasattr(check, "notification_check") else None
[docs]
self.check_url = check.notification_check.url if hasattr(check, "notification_check") else None
[docs]
self.check_id = check.notification_check.id if hasattr(check, "notification_check") else None
[docs]
self.spotted_at = datetime.utcnow().replace(tzinfo=pytz.utc)
[docs]
self.description = description
[docs]
self.valid_from = metric.valid_from if hasattr(metric, "valid_from") else None
[docs]
self.valid_to = metric.valid_to if hasattr(metric, "valid_to") else None
[docs]
def __str__(self):
return f"MetricValueError({self.severity}: metric {self.metric} misses {self.check} check: {self.message})"
[docs]
def check_value(self, metric, valid_on):
"""
Check specific metric if it's faulty or not.
"""
v = metric.value_num
m = metric.service_metric.metric
metric_name = m.description or m.name
unit_name = f" {m.unit}" if not m.is_count else ""
had_check = False
if self.definition:
def_msg = self.definition.description
msg_prefix = []
if self.event_type:
os = self.event_type
if os.is_all or os.is_other:
msg_prefix.append(f"for {os.name} OWS")
else:
msg_prefix.append(f"for {os.name} OWS")
if self.service:
msg_prefix.append(f"for {self.service.name} service")
if self.resource:
msg_prefix.append(f"for {self.resource.name}[{self.resource.type}] resource")
msg_prefix = " ".join(msg_prefix)
description_tmpl = (
f"{msg_prefix} {metric_name} should be {{}} " f"{{:0.0f}}{unit_name}, got {{:0.0f}}{unit_name} instead"
).strip()
if v is not None and self.min_value is not None:
had_check = True
if float(v) < float(self.min_value):
msg = f"{def_msg} {int(self.min_value)} {unit_name}"
description = description_tmpl.format("at least", float(self.min_value), float(v))
raise self.MetricValueError(metric, self, msg, v, self.min_value, description)
if v is not None and self.max_value is not None:
had_check = True
if float(v) > float(self.max_value):
msg = f"{def_msg} {int(self.max_value)} {unit_name}"
description = description_tmpl.format("at most", float(self.max_value), float(v))
raise self.MetricValueError(metric, self, msg, v, self.max_value, description)
if self.max_timeout is not None:
had_check = True
# we have to check for now, because valid_on may be in the past,
# metric may be at the valid_on point in time
valid_on = datetime.utcnow().replace(tzinfo=pytz.utc)
metric.valid_to = metric.valid_to.replace(tzinfo=pytz.utc)
if (valid_on - metric.valid_to) > self.max_timeout:
total_seconds = self.max_timeout.total_seconds()
actual_seconds = (valid_on - metric.valid_to).total_seconds()
msg = f"{def_msg} {int(total_seconds)} seconds"
description = description_tmpl.format(
"recored at most ", f"{total_seconds} seconds ago", f"{actual_seconds} seconds"
)
raise self.MetricValueError(metric, self, msg, metric.valid_to, valid_on, description)
if not had_check:
raise self.MetricValueError(metric, self, "", None, None, f"Metric check {self} is not checking anything")
[docs]
def check_metric(self, for_timestamp=None):
""" """
if not for_timestamp:
for_timestamp = datetime.utcnow().replace(tzinfo=pytz.utc)
qfilter = {"metric": self.metric}
if self.service:
qfilter["service"] = self.service
if self.resource:
qfilter["resource"] = self.resource
if self.label:
qfilter["label"] = self.label
if self.event_type:
qfilter["event_type"] = self.event_type
if self.max_timeout is None:
metrics = MetricValue.get_for(valid_on=for_timestamp, **qfilter)
else:
metrics = MetricValue.get_for(**qfilter)
if not metrics:
raise self.MetricValueError(
self.metric, "", "", None, None, f"Cannot find metric values for {self.metric} on {for_timestamp}"
)
for m in metrics:
self.check_value(m, for_timestamp)
return True
[docs]
class BuiltIns:
[docs]
service_types = (
ServiceType.TYPE_GEONODE,
ServiceType.TYPE_GEOSERVER,
)
[docs]
host_service_types = (
ServiceType.TYPE_HOST_GN,
ServiceType.TYPE_HOST_GS,
)
[docs]
metrics_rate = (
"response.time",
"response.size",
)
# metrics_count = ('request.count', 'request.method', 'request.
[docs]
geonode_metrics = (
"request",
"request.count",
"request.users",
"request.ip",
"request.ua",
"request.path",
"request.ua.family",
"request.method",
"response.error.count",
"request.country",
"request.region",
"request.city",
"response.time",
"response.status",
"response.size",
"response.error.types",
)
[docs]
host_metrics = (
"load.1m",
"load.5m",
"load.15m",
"mem.free",
"mem.usage",
"mem.usage.percent",
"mem.buffers",
"mem.all",
"uptime",
"cpu.usage",
"cpu.usage.rate",
"cpu.usage.percent",
"storage.free",
"storage.total",
"storage.used",
# mountpoint is the label
"network.in",
"network.out",
"network.in.rate",
"network.out.rate",
)
[docs]
rates = (
"response.time",
"response.size",
"network.in.rate",
"network.out.rate",
"load.1m",
"load.5m",
"load.15m",
"cpu.usage.rate",
"cpu.usage.percent",
"cpu.usage",
"mem.usage.percent",
"storage.free",
"storage.total",
"storage.used",
)
[docs]
values = (
"request.ip",
"request.ua",
"request.ua.family",
"request.path",
"request.method",
"request.country",
"request.region",
"request.city",
"response.status",
"response.ereror.types",
"request.users",
)
[docs]
values_numeric = (
"storage.total",
"storage.used",
"storage.free",
"mem.free",
"mem.usage",
"mem.buffers",
"mem.all",
)
[docs]
counters = (
"request.count",
"network.in",
"network.out",
"response.error.count",
"uptime",
)
[docs]
unit_seconds = (
"response.time",
"uptime",
"cpu.usage",
)
[docs]
unit_bytes = (
"response.size",
"network.in",
"network.out",
"mem.free",
"mem.usage",
"mem.buffers",
"mem.all",
)
[docs]
unit_bps = (
"network.in.rate",
"network.out.rate",
)
[docs]
unit_rate = (
"cpu.usage.rate",
"load.1m",
"load.5m",
"load.15m",
)
[docs]
unit_percentage = (
"cpu.usage.percent",
"mem.usage.percent",
)
[docs]
descriptions = {
"request.count": "Number of requests",
"request.users": "Number of users visiting",
"response.time": "Time of making a response",
"request.ip": "IP Address of source of request",
"request.ua": "User Agent of source of request",
"request.path": "Request URL",
"network.in.rate": "Network incoming traffic rate",
"network.out.rate": "Network outgoing traffic rate",
"network.out": "Network outgoing traffic bytes",
"network.in": "Network incoming traffic bytes",
}
[docs]
def populate():
for m in BuiltIns.geonode_metrics + BuiltIns.host_metrics:
Metric.objects.get_or_create(name=m)
for st in BuiltIns.service_types + BuiltIns.host_service_types:
ServiceType.objects.get_or_create(name=st)
for st in BuiltIns.service_types:
for m in BuiltIns.geonode_metrics:
_st = ServiceType.objects.get(name=st)
_m = Metric.objects.get(name=m)
ServiceTypeMetric.objects.get_or_create(service_type=_st, metric=_m)
for st in BuiltIns.host_service_types:
for m in BuiltIns.host_metrics:
_st = ServiceType.objects.get(name=st)
_m = Metric.objects.get(name=m)
ServiceTypeMetric.objects.get_or_create(service_type=_st, metric=_m)
Metric.objects.filter(name__in=BuiltIns.counters).update(type=Metric.TYPE_COUNT)
Metric.objects.filter(name__in=BuiltIns.rates).update(type=Metric.TYPE_RATE)
Metric.objects.filter(name__in=BuiltIns.values).update(type=Metric.TYPE_VALUE)
Metric.objects.filter(name__in=BuiltIns.values_numeric).update(type=Metric.TYPE_VALUE_NUMERIC)
for etype, etype_name in EventType.EVENT_TYPES:
EventType.objects.get_or_create(name=etype)
for attr_name in dir(BuiltIns):
if not attr_name.startswith("unit_"):
continue
val = getattr(BuiltIns, attr_name)
uname = getattr(Metric, attr_name.upper())
for mname in val:
m = Metric.objects.get(name=mname)
m.unit = uname
m.save()
Metric.objects.filter(unit__isnull=True).update(unit=Metric.UNIT_COUNT)
for m, d in BuiltIns.descriptions.items():
metric = Metric.objects.get(name=m)
metric.description = d
metric.save()
if not Service.objects.all():
do_autoconfigure()
[docs]
def do_reload():
"""
This will reload uwsgi if it's available
"""
try:
import uwsgi
uwsgi.reload()
return
except ImportError:
pass