Skip to content
Snippets Groups Projects
Commit b35082e6 authored by Dmytro Bogatov's avatar Dmytro Bogatov :two_hearts:
Browse files

Resolve conflicts.

parents c508f2b3 a0aebcbe
Branches
No related tags found
No related merge requests found
......@@ -89,6 +89,7 @@ ServiceManager:
NotificationService:
Enabled: true
Interval: 30
TimeZone: "America/New_York"
Frequencies:
Low: 86400
Medium: 360
......
......
......@@ -132,7 +132,7 @@ namespace StatusMonitor.Daemons.Services
foreach (var discrepancy in unique)
{
await _notification.ScheduleNotificationAsync(
discrepancy.ToString(),
discrepancy.ToStringWithTimeZone(_config["ServiceManager:NotificationService:TimeZone"]),
NotificationSeverity.High
);
}
......@@ -162,11 +162,11 @@ namespace StatusMonitor.Daemons.Services
_logger.LogDebug(
LoggingEvents.Discrepancies.AsInt(),
$"Discrepancy \"{discrepancy.ToString()}\" has been resolved!"
$"Discrepancy \"{discrepancy.ToStringWithTimeZone()}\" has been resolved!"
);
await _notification.ScheduleNotificationAsync(
$"Discrepancy \"{discrepancy.ToString()}\" has been resolved!",
$"Discrepancy \"{discrepancy.ToStringWithTimeZone(_config["ServiceManager:NotificationService:TimeZone"])}\" has been resolved!",
NotificationSeverity.High
);
}
......@@ -257,8 +257,9 @@ namespace StatusMonitor.Daemons.Services
}
var ordered = timestamps.OrderBy(dp => dp);
var maxDiff = new TimeSpan(0, 0, (int)Math.Round(Convert.ToInt32(_config["ServiceManager:DiscrepancyService:Gaps:MaxDifference"]) * 1.5));
return ordered
var result = ordered
.Zip(
ordered.Skip(1),
(x, y) => new
......@@ -267,13 +268,7 @@ namespace StatusMonitor.Daemons.Services
DateFirstOffense = x
}
)
.Where(
x => x.Difference >= new TimeSpan(
0,
0,
(int)Math.Round(Convert.ToInt32(_config["ServiceManager:DiscrepancyService:Gaps:MaxDifference"]) * 1.5)
)
)
.Where(x => x.Difference >= maxDiff)
.Select(x => new Discrepancy
{
DateFirstOffense = x.DateFirstOffense,
......@@ -282,6 +277,19 @@ namespace StatusMonitor.Daemons.Services
MetricSource = metric.Source
})
.ToList();
if (DateTime.UtcNow - ordered.Last() >= maxDiff)
{
result.Add(new Discrepancy
{
DateFirstOffense = ordered.Last(),
Type = DiscrepancyType.GapInData,
MetricType = (Metrics)metric.Type,
MetricSource = metric.Source
});
}
return result;
}
/// <summary>
......@@ -315,6 +323,7 @@ namespace StatusMonitor.Daemons.Services
return failures
.OrderBy(p => p.Timestamp)
.SkipWhile(p => !p.StatusOK)
.Aggregate(
new Stack<BoolIntDateTuple>(),
(rest, self) =>
......@@ -376,6 +385,7 @@ namespace StatusMonitor.Daemons.Services
return loads
.OrderBy(p => p.Timestamp)
.SkipWhile(p => !p.NormalLoad)
.Aggregate(
new Stack<BoolIntDateTuple>(),
(rest, self) =>
......
......
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using StatusMonitor.Shared.Models.Entities;
namespace StatusMonitor.Shared.Extensions
{
public static class DateTimeExtensions
{
/// <summary>
/// Calls .ToString method on a date converted from UTC to given time zone.
/// </summary>
/// <param name="value">The date to convert to string</param>
/// <param name="timeZoneId">ID of a time zone (eq. "America/New_York").
/// Does not change date object if this parameter is omitted.</param>
/// <returns>String representation of a correct date.</returns>
public static string ToStringUsingTimeZone(this DateTime value, string timeZoneId = null)
{
return
timeZoneId == null
? value.ToString()
: TimeZoneInfo.ConvertTime(value, TimeZoneInfo.FindSystemTimeZoneById(timeZoneId)).ToString();
}
}
}
......@@ -25,20 +25,22 @@ namespace StatusMonitor.Shared.Models.Entities
public DateTime DateResolved { get; set; }
public bool Resolved { get; set; } = false;
public override string ToString()
public string ToStringWithTimeZone(string timeZoneId = null)
{
switch (Type)
{
case DiscrepancyType.GapInData:
return $"Gap in data from {MetricSource} has been detected. The gap starts on {DateFirstOffense}.";
return $"Gap in data from {MetricSource} has been detected. The gap starts on {DateFirstOffense.ToStringUsingTimeZone(timeZoneId)}.";
case DiscrepancyType.HighLoad:
return $"{MetricSource} reported high load starting from {DateFirstOffense}.";
return $"{MetricSource} reported high load starting from {DateFirstOffense.ToStringUsingTimeZone(timeZoneId)}.";
case DiscrepancyType.PingFailedNTimes:
return $"Requests to {MetricSource} failed too many consecutive times. First failure occurred on {DateFirstOffense}.";
return $"Requests to {MetricSource} failed too many consecutive times. First failure occurred on {DateFirstOffense.ToStringUsingTimeZone(timeZoneId)}.";
default:
return $"Discrepancy of an unknown type from {MetricSource} has been detected. It started on {DateFirstOffense}.";
return $"Discrepancy of an unknown type from {MetricSource} has been detected. It started on {DateFirstOffense.ToStringUsingTimeZone(timeZoneId)}.";
}
}
public override string ToString() => ToStringWithTimeZone();
}
public enum DiscrepancyType
......
......
......@@ -166,7 +166,7 @@ namespace StatusMonitor.Shared.Services
(value, key) => $@"Severity {(NotificationSeverity)value.Key}:{Environment.NewLine}
{
value
.Select(ntf => $"[{ntf.DateCreated}] {ntf.Message}")
.Select(ntf => $"[{ntf.DateCreated.ToStringUsingTimeZone(_config["ServiceManager:NotificationService:TimeZone"])}] {ntf.Message}")
.Aggregate((self, next) => $"{self}{Environment.NewLine}{next}")
}
"
......
......
......@@ -263,6 +263,71 @@ namespace StatusMonitor.Tests.UnitTests.Services
Assert.Equal(expected, actual);
}
[Fact]
public async Task ReportsGapIfServerIsDown()
{
// Arrange
var mockConfig = new Mock<IConfiguration>();
mockConfig
.SetupGet(conf => conf["ServiceManager:DiscrepancyService:Gaps:MaxDifference"])
.Returns(60.ToString());
mockConfig
.SetupGet(conf => conf["ServiceManager:DiscrepancyService:DataTimeframe"])
.Returns(1800.ToString());
var context = _serviceProvider.GetRequiredService<IDataContext>();
var metric = await context.Metrics.AddAsync(
new Metric
{
Source = "the-source",
Type = Metrics.CpuLoad.AsInt()
}
);
var dataPoints = new List<NumericDataPoint> {
new NumericDataPoint {
Timestamp = DateTime.UtcNow.AddMinutes(-2),
Metric = metric.Entity
},
new NumericDataPoint {
Timestamp = DateTime.UtcNow.AddMinutes(-3),
Metric = metric.Entity
},
new NumericDataPoint {
Timestamp = DateTime.UtcNow.AddMinutes(-4),
Metric = metric.Entity
}
};
await context.NumericDataPoints.AddRangeAsync(dataPoints);
await context.SaveChangesAsync();
var discrepancyService = new DiscrepancyService(
new Mock<ILogger<DiscrepancyService>>().Object,
context,
new Mock<INotificationService>().Object,
mockConfig.Object
);
var expected = new List<Discrepancy> {
new Discrepancy
{
DateFirstOffense = dataPoints[0].Timestamp,
Type = DiscrepancyType.GapInData,
MetricType = Metrics.CpuLoad,
MetricSource = "the-source"
}
};
// Act
var actual = await discrepancyService
.FindGapsAsync(
metric.Entity,
new TimeSpan(0, 30, 0)
);
// Assert
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(Metrics.Compilation, false)]
[InlineData(Metrics.CpuLoad, true)]
......
......
......@@ -350,6 +350,85 @@ namespace StatusMonitor.Tests.UnitTests.Services
Assert.Equal(expected, actual);
}
[Fact]
public void HighLoadDataStartsWithHighLoad()
{
// Arrange
var mockConfig = new Mock<IConfiguration>();
mockConfig
.SetupGet(conf => conf["ServiceManager:DiscrepancyService:Load:Threshold"])
.Returns(90.ToString());
mockConfig
.SetupGet(conf => conf["ServiceManager:DiscrepancyService:Load:MaxFailures"])
.Returns(2.ToString()); // 3 is discrepancy
var discrepancyService = new DiscrepancyService(
new Mock<ILogger<DiscrepancyService>>().Object,
new Mock<IDataContext>().Object,
new Mock<INotificationService>().Object,
mockConfig.Object
);
var input = new List<NumericDataPoint>() {
new NumericDataPoint { // Good
Timestamp = DateTime.UtcNow.AddMinutes(0),
Value = 68
},
new NumericDataPoint { // Bad
Timestamp = DateTime.UtcNow.AddMinutes(-1),
Value = 91
},
new NumericDataPoint { // Bad
Timestamp = DateTime.UtcNow.AddMinutes(-2),
Value = 99
},
new NumericDataPoint { // Bad
Timestamp = DateTime.UtcNow.AddMinutes(-3),
Value = 95
},
new NumericDataPoint { // Good
Timestamp = DateTime.UtcNow.AddMinutes(-4),
Value = 68
},
new NumericDataPoint { // Bad
Timestamp = DateTime.UtcNow.AddMinutes(-5),
Value = 99
},
new NumericDataPoint { // Bad
Timestamp = DateTime.UtcNow.AddMinutes(-6),
Value = 98
},
new NumericDataPoint { // Bad
Timestamp = DateTime.UtcNow.AddMinutes(-7),
Value = 92
}
};
var expected = new List<Discrepancy> {
new Discrepancy
{
DateFirstOffense = input[3].Timestamp,
Type = DiscrepancyType.HighLoad,
MetricType = Metrics.CpuLoad,
MetricSource = "the-source"
}
};
// Act
var actual = discrepancyService
.FindHighLoadInDataPoints(
input,
new Metric
{
Type = Metrics.CpuLoad.AsInt(),
Source = "the-source"
}
);
// Assert
Assert.Equal(expected, actual);
}
[Fact]
public async Task FindsHighLoad()
{
......
......
......@@ -219,34 +219,108 @@ namespace StatusMonitor.Tests.UnitTests.Services
var input = new List<PingDataPoint>() {
new PingDataPoint {
Success = true,
Timestamp = DateTime.UtcNow - new TimeSpan(0, 0, 0)
Timestamp = DateTime.UtcNow.AddMinutes(0)
},
new PingDataPoint {
Success = false,
Timestamp = DateTime.UtcNow - new TimeSpan(0, 1, 0)
Timestamp = DateTime.UtcNow.AddMinutes(-1)
},
new PingDataPoint {
Success = false,
Timestamp = DateTime.UtcNow - new TimeSpan(0, 2, 0)
Timestamp = DateTime.UtcNow.AddMinutes(-2)
},
new PingDataPoint {
Success = true,
Timestamp = DateTime.UtcNow - new TimeSpan(0, 3, 0)
Timestamp = DateTime.UtcNow.AddMinutes(-3)
},
new PingDataPoint {
Success = false,
Timestamp = DateTime.UtcNow - new TimeSpan(0, 4, 0)
Timestamp = DateTime.UtcNow.AddMinutes(-4)
},
new PingDataPoint {
Success = false,
Timestamp = DateTime.UtcNow.AddMinutes(-5)
},
new PingDataPoint {
Success = true,
Timestamp = DateTime.UtcNow.AddMinutes(-6)
}
};
var expected = new List<Discrepancy> {
new Discrepancy
{
DateFirstOffense = input[4].Timestamp,
DateFirstOffense = input[5].Timestamp,
Type = DiscrepancyType.PingFailedNTimes,
MetricType = Metrics.Ping,
MetricSource = "the-source"
},
new Discrepancy
{
DateFirstOffense = input[2].Timestamp,
Type = DiscrepancyType.PingFailedNTimes,
MetricType = Metrics.Ping,
MetricSource = "the-source"
}
}
.OrderBy(d => d.DateFirstOffense); ;
// Act
var actual = discrepancyService
.FindPingFailuresFromDataPoints(
input,
new PingSetting { MaxFailures = 0 },
new Metric
{
Type = Metrics.Ping.AsInt(),
Source = "the-source"
}
)
.OrderBy(d => d.DateFirstOffense); ;
// Assert
Assert.Equal(expected, actual);
}
[Fact]
public void PingFailuresDataStartsWithFailure()
{
// Arrange
var discrepancyService = new DiscrepancyService(
new Mock<ILogger<DiscrepancyService>>().Object,
new Mock<IDataContext>().Object,
new Mock<INotificationService>().Object,
new Mock<IConfiguration>().Object
);
var input = new List<PingDataPoint>() {
new PingDataPoint {
Success = true,
Timestamp = DateTime.UtcNow.AddMinutes(0)
},
new PingDataPoint {
Success = false,
Timestamp = DateTime.UtcNow.AddMinutes(-1)
},
new PingDataPoint {
Success = false,
Timestamp = DateTime.UtcNow.AddMinutes(-2)
},
new PingDataPoint {
Success = true,
Timestamp = DateTime.UtcNow.AddMinutes(-3)
},
new PingDataPoint {
Success = false,
Timestamp = DateTime.UtcNow.AddMinutes(-4)
},
new PingDataPoint {
Success = false,
Timestamp = DateTime.UtcNow.AddMinutes(-5)
}
};
var expected = new List<Discrepancy> {
new Discrepancy
{
DateFirstOffense = input[2].Timestamp,
......
......
......@@ -171,5 +171,48 @@ namespace StatusMonitor.Tests.UnitTests.Services
// Assert
Assert.Empty(actual);
}
[Fact]
public async Task UsesCorrectTimeZone()
{
// Arrange
var config = new Mock<IConfiguration>();
config
.SetupGet(conf => conf["ServiceManager:NotificationService:TimeZone"])
.Returns("Asia/Kabul");
var date = DateTime.SpecifyKind(new DateTime(2017, 07, 14, 18, 25, 43), DateTimeKind.Utc);
var context = _serviceProvider.GetRequiredService<IDataContext>();
var mockNotifications = new Mock<INotificationService>();
var discrepancyService = new DiscrepancyService(
new Mock<ILogger<DiscrepancyService>>().Object,
context,
mockNotifications.Object,
config.Object
);
var input = new List<Discrepancy> {
new Discrepancy {
DateFirstOffense = date,
Type = DiscrepancyType.GapInData,
MetricSource = "the-source",
MetricType = Metrics.CpuLoad
}
};
// Act
await discrepancyService.RecordDiscrepanciesAsync(input);
// Assert
mockNotifications
.Verify(
n => n.ScheduleNotificationAsync(
It.Is<string>(s => s.Contains(date.ToStringUsingTimeZone("Asia/Kabul"))),
NotificationSeverity.High
)
);
}
}
}
......@@ -338,5 +338,40 @@ namespace StatusMonitor.Tests.UnitTests.Services
Times.Never()
);
}
[Fact]
public void UsesCorrectTimeZone()
{
// Arrange
var config = new Mock<IConfiguration>();
config
.SetupGet(conf => conf["ServiceManager:NotificationService:TimeZone"])
.Returns("Asia/Kabul");
var notificationService = new NotificationService(
new Mock<ILogger<NotificationService>>().Object,
config.Object,
new Mock<IDataContext>().Object,
new Mock<IEmailService>().Object,
new Mock<ISlackService>().Object
);
var date = DateTime.SpecifyKind(new DateTime(2017, 07, 14, 18, 25, 43), DateTimeKind.Utc);
var input = new List<Notification> {
new Notification {
DateCreated = date,
Message = $"The message.",
Severity = NotificationSeverity.Medium
}
};
var expected = date.ToStringUsingTimeZone("Asia/Kabul");
// Act
var actual = notificationService.ComposeMessage(input);
// Assert
Assert.Contains(expected, actual);
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment