Skip to content

Commit

Permalink
added uptime impementation
Browse files Browse the repository at this point in the history
  • Loading branch information
yigagilbert committed Feb 12, 2024
1 parent b982c83 commit 20e6386
Show file tree
Hide file tree
Showing 16 changed files with 637 additions and 143 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
myenv/
venv/
__pycache__
.coverage
Expand Down
5 changes: 5 additions & 0 deletions devices/forms.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django import forms
from django.forms import ModelForm, ValidationError
from .models import Device, Location
import re
Expand Down Expand Up @@ -34,3 +35,7 @@ class DeviceConfigurationForm(ModelForm):
class Meta:
model = Device
fields = ['configured', 'mode', 'dbLevel', 'recLength', 'recInterval', 'uploadAddr']


class UptimeDurationForm(ModelForm):
duration_weeks = forms.IntegerField()
177 changes: 177 additions & 0 deletions devices/models.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import uuid
import calendar
from django.utils.translation import gettext_lazy as _

from django.db import models
from django.urls import reverse
import pytz
from noise_dashboard.settings import TIME_ZONE

from taggit.managers import TaggableManager
from taggit.models import GenericUUIDTaggedItemBase, TaggedItemBase

from datetime import datetime, timedelta
from django.utils import timezone


class UUIDTaggedItem(GenericUUIDTaggedItemBase, TaggedItemBase):
Expand Down Expand Up @@ -58,6 +62,22 @@ class Mode(models.IntegerChoices):
recInterval = models.IntegerField(default=10)
uploadAddr = models.CharField(default="http://localhost:8000/audio/", max_length=100)

@property
def lastseen(self):
if last_metric := self.get_last_metric_text_file():
return last_metric.time_uploaded
else:
# Define a default value or handle this case as needed
return None

def get_last_metric_text_file(self):
try:
# Retrieve the last metricstextfile uploaded for this device
return self.metricstextfile_set.order_by('-time_uploaded').first()
except self.metricstextfile_set.model.DoesNotExist:
# Handle the case where there are no metricstextfiles
return None

def __str__(self):
return self.device_id

Expand All @@ -75,6 +95,159 @@ def get_metric_files(self):

def get_absolute_url(self):
return reverse("device_detail", args=[str(self.id)])

@property
def uptime(self):
"""
Calculate and return the uptime of the device.
"""
return self.calculate_uptime(False)


timezone = pytz.timezone(TIME_ZONE)


def calculate_uptime(self, audio=True):
"""
Returns gaps between upload times and total uptime. Returns a dictionary with:
upload_gaps: Long gaps between uploads (1.5 hours or more)
uptime: Number of hours/days since there was a 24-hour upload gap.
"""

big_gaps = []
went_online = None
previous_downtime = None

now = datetime.now(self.timezone)
delta = timedelta(weeks=4)
start_time = now - delta
uploaded_times = self.get_uploaded_times(audio, now, start_time)
uptime_percentages = self.calculate_monthly_uptime(uploaded_times)
months = self.get_next_12_months()

if not uploaded_times:
return {
'big_gaps': [],
'uptime': 0,
'previous_downtime': None,
'uptime_percentages': [],
}

for date_from, date_to in zip(uploaded_times[:-1], uploaded_times[1:]):
gap_time = date_to - date_from
gap = gap_time.total_seconds() / 3600

if gap > 3.0:
big_gaps.append((gap, f"From {date_from} to {date_to}"))

if gap >= 24 and not went_online:
went_online = date_to
previous_downtime = f"From {date_from} to {date_to}"

if not went_online:
went_online = start_time

def_time = now - went_online
uptime = max(0, def_time.total_seconds() / 3600)

return {
'upload_gaps': big_gaps,
'uptime': uptime,
'previous_downtime': previous_downtime,
'uptime_percentages': zip(months, uptime_percentages)
}

def get_uploaded_times(self, audio, now, start_time):
query_set = self.recording_set if audio else self.metricstextfile_set

try:
files_dates = (query_set.filter(time_uploaded__range=[start_time, now])
.values_list('time_uploaded', flat=True)
.order_by('time_uploaded'))
return list(files_dates)
except query_set.model.DoesNotExist:
# Handle the case where there are no metricstextfiles
return []

def get_next_12_months(self):
current_month = datetime.now().replace(day=1) # Get the first day of the current month
months = [current_month]

for _ in range(11):
current_month = current_month + timedelta(days=31) # Add an arbitrary number of days to move to the next month
current_month = current_month.replace(day=1) # Set the day to 1 to get the first day of the month
months.append(current_month)

return months

# def calculate_monthly_uptime(self, uploaded_times):
# current_month = datetime.now().month
# uptime_per_month = [0] * 12

# for upload_time in uploaded_times:
# month = upload_time.month - 1 # Adjust to 0-indexed
# uptime_per_month[month] += 1

# total_months = len(uptime_per_month)
# current_month_index = (current_month - 1) % total_months

# # Calculate uptime percentages
# uptime_percentages = [(count / (4*7*24)) * 100 for count in uptime_per_month]

# # Shift the list so that the current month is at the beginning
# uptime_percentages = uptime_percentages[current_month_index:] + uptime_percentages[:current_month_index]

# return uptime_percentages

# Import the calendar module


def calculate_monthly_uptime(self, uploaded_times):
current_month = datetime.now().month
uptime_per_month = [0] * 12

for upload_time in uploaded_times:
month = upload_time.month - 1 # Adjust to 0-indexed
uptime_per_month[month] += 1

total_months = len(uptime_per_month)
current_month_index = (current_month - 1) % total_months

# Calculate uptime percentages
uptime_percentages = [(count / (days * 24)) * 100 for count, days in zip(uptime_per_month, [calendar.monthrange(2024, i)[1] for i in range(1, 13)])]

# Shift the list so that the current month is at the beginning
uptime_percentages = uptime_percentages[current_month_index:] + uptime_percentages[:current_month_index]

return uptime_percentages

def filter_time_by_month_and_year(self, time_formats, month, year):
# Create an empty list to store the filtered time formats
filtered_time_formats = []
# Loop through each time format in the list
for time_format in time_formats:
# Parse the time format into a datetime object using the isoformat method
datetime_object = datetime.datetime.fromisoformat(time_format)
# Check if the month and year attributes of the datetime object match the given month and year
if datetime_object.month == month and datetime_object.year == year:
# If yes, append the time format to the filtered list
filtered_time_formats.append(time_format)
# Return the filtered list
return filtered_time_formats


def calculate_uptime_percentage(self, duration_weeks=4):
"""
Calculate and return the uptime percentage of the device for a given duration.
"""
now = datetime.now(self.timezone)
delta = timedelta(weeks=duration_weeks)
start_time = now - delta
uptime_data = self.calculate_uptime()

total_hours_in_duration = duration_weeks * 7 * 24
return (uptime_data['uptime'] / total_hours_in_duration) * 100



location_category_information = {
Expand Down Expand Up @@ -145,6 +318,10 @@ def night_limit(self):
@property
def day_limit(self):
return location_category_information[self.category]['day_limit']

@property
def latest_audio(self):
return self.device.location_recordings.order_by("-date")[0]

# @property
# def latest_metric(self):
Expand Down
2 changes: 1 addition & 1 deletion devices/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class RecordingSerializer(serializers.ModelSerializer):

class Meta:
model = Recording
fields = ['id', 'time_recorded', 'category', 'audio', 'triggering_threshold']
fields = ['id', 'time_recorded','device', 'category', 'audio', 'triggering_threshold']
read_only_fields = ['time_uploaded']


Expand Down
71 changes: 71 additions & 0 deletions devices/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@

from celery.decorators import task
import smtplib
from email.mime.text import MIMEText
from devices.models import Device
import pytz
from noise_dashboard.settings import TIME_ZONE



@task(name="send_email_alert")
def send_email_alert():
# Get all devices
devices = Device.objects.all()
timezone = pytz.timezone(TIME_ZONE)

for device in devices:
# Get last seen time
last_seen_time = device.lastseen

# Check for conditions and send emails
if last_seen_time is not None:
current_time = timezone.now()
uptime_hours = (current_time - last_seen_time).total_seconds() / 3600

if uptime_hours >= 120:
send_critical_email(device)
elif uptime_hours >= 72:
send_flagged_email(device)
elif uptime_hours >= 24:
send_device_off_email(device)


def send_critical_email(device):
subject = 'Critical Uptime Alert'
message = f'The device {device.device_name} has been offline for more than 5 days.'
send_email(subject, message)

def send_flagged_email(device):
subject = 'Flagged Uptime Alert'
message = f'The device {device.device_name} has been offline for more than 3 days.'
send_email(subject, message)

def send_device_off_email(device):
subject = 'Device Offline Alert'
message = f'The device {device.device_name} has been offline for 24 hours.'
send_email(subject, message)

def send_email(subject, message):
# Modify the email sending logic based on your project's email settings
# send_mail(subject, message, 'vigidaiz15@gmail.com', ['gilbertyiga15@gmail.com'], fail_silently=False,)
from_email = "vigidaiz15@gmail.com"
to_email = "gilbertyiga15@gmail.com"
password = "mfww omdz cowe ipgt"

try:
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
server.login(from_email, password)

message = MIMEText(message)
message['From'] = from_email
message['To'] = to_email
message['Subject'] = subject

server.sendmail(from_email, to_email, message.as_string())
server.quit()

print("Email sent!")
except Exception as e:
print(f"Error sending email: {e}")
33 changes: 15 additions & 18 deletions devices/uptime_calculation.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
timezone = pytz.timezone(TIME_ZONE)


def calculate_uptime(device_id, weeks_past, audio=True):
def calculate_uptime(device, weeks_past, audio=True):
"""
Returns gaps between upload times and total uptime. Returns a dictionary with:
upload_gaps: Long gaps between uploads (1.5 hours or more)
Expand All @@ -15,18 +15,15 @@ def calculate_uptime(device_id, weeks_past, audio=True):
now = timezone.localize(datetime.now())
delta = timedelta(weeks=weeks_past)
start_time = now - delta
uploaded_times = get_uploaded_times(device_id, audio, now, start_time)
uploaded_times = get_uploaded_times(device, audio, now, start_time)
return get_gaps(uploaded_times, now, start_time)


def timedelta_to_hours(delta: timedelta):
seconds = delta.total_seconds()
hours = seconds / 3600
return hours
return delta.total_seconds() / 3600


def get_uploaded_times(device_id, audio, now, start_time):
device = Device.objects.get(device_id=device_id)
def get_uploaded_times(device, audio, now, start_time):
query_set = device.recording_set if audio else device.metricstextfile_set
files_dates = (query_set.filter(time_uploaded__range=[start_time, now])
.values_list('time_uploaded', flat=True)
Expand All @@ -35,32 +32,32 @@ def get_uploaded_times(device_id, audio, now, start_time):


def get_gaps(uploaded_times, now, start_time):
if len(uploaded_times) == 0:
big_gaps = []
went_online = None
previous_downtime = None

if not uploaded_times:
return {
'big_gaps': [],
'uptime': 0,
'previous_downtime': None
}
big_gaps = []
went_online = None
previous_downtime = None
for i in range(1, len(uploaded_times)):
date_to = uploaded_times[i - 1]
date_from = uploaded_times[i]

for date_from, date_to in zip(uploaded_times[:-1], uploaded_times[1:]):
gap = timedelta_to_hours(date_to - date_from)

if gap > 1.5:
big_gaps.append((gap, f"From {date_from} to {date_to}"))

if gap >= 24 and not went_online:
went_online = date_to
previous_downtime = f"From {date_from} to {date_to}"

if not went_online:
went_online = start_time

if timedelta_to_hours(now - uploaded_times[0]) >= 24:
uptime = 0
else:
uptime = timedelta_to_hours(now - went_online)
uptime = max(0, timedelta_to_hours(now - went_online))

return {
'upload_gaps': big_gaps,
'uptime': uptime,
Expand Down
Loading

0 comments on commit 20e6386

Please sign in to comment.