Skip to content

Commit e4b98af

Browse files
MartinNikovPetarKirov
authored andcommitted
feat(packages): Add random-alerts package
1 parent 0846acd commit e4b98af

File tree

7 files changed

+254
-0
lines changed

7 files changed

+254
-0
lines changed

packages/default.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
{
5858
lido-withdrawals-automation = pkgs.callPackage ./lido-withdrawals-automation { };
5959
pyroscope = pkgs.callPackage ./pyroscope { };
60+
random-alerts = pkgs.callPackage ./random-alerts { };
6061
}
6162
// optionalAttrs (system == "x86_64-linux" || system == "aarch64-darwin") {
6263
grafana-agent = import ./grafana-agent { inherit inputs'; };

packages/random-alerts/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build

packages/random-alerts/default.nix

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{ buildDubPackage, ... }:
2+
buildDubPackage rec {
3+
pname = "random-alerts";
4+
version = "1.0.0";
5+
src = ./.;
6+
dubLock = {
7+
dependencies = { };
8+
};
9+
installPhase = ''
10+
mkdir -p $out/bin
11+
install -m755 ./build/${pname} $out/bin/${pname}
12+
'';
13+
meta.mainProgram = pname;
14+
}

packages/random-alerts/dub.sdl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
name "random-alerts"
2+
3+
targetPath "build"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"fileVersion": 1,
3+
"versions": {
4+
}
5+
}

packages/random-alerts/src/main.d

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import core.thread : Thread;
2+
import std.datetime : Duration, Clock, seconds, TimeOfDay;
3+
import std.format : format;
4+
import std.getopt : getopt, getOptConfig = config;
5+
import std.json : JSONValue, parseJSON, JSONOptions;
6+
import std.logger : infof, errorf, tracef, LogLevel;
7+
import std.random : uniform;
8+
import std.exception : enforce;
9+
10+
import utils.json : toJSON;
11+
12+
struct Params
13+
{
14+
Duration minWaitTime;
15+
Duration maxWaitTime;
16+
Duration alertDuration;
17+
string url;
18+
TimeOfDay startTime;
19+
TimeOfDay endTime;
20+
}
21+
22+
struct Alert
23+
{
24+
string[string] labels;
25+
Annotation annotations;
26+
string startsAt;
27+
string endsAt;
28+
string generatorURL;
29+
30+
struct Annotation
31+
{
32+
string alert_type;
33+
string title;
34+
string summary;
35+
}
36+
}
37+
38+
int main(string[] args)
39+
{
40+
LogLevel logLevel = LogLevel.info;
41+
string url;
42+
string startTime = "00:00:00";
43+
string endTime = "23:59:59";
44+
uint minWaitTimeInSeconds = 3600; // 1 hour
45+
uint maxWaitTimeInSeconds = 14400; // 4 hours
46+
uint alertDurationInSeconds = 3600; // 1 hour
47+
48+
try
49+
{
50+
args.getopt(
51+
getOptConfig.required, "url", &url,
52+
"start-time", &startTime,
53+
"end-time", &endTime,
54+
"min-wait-time", &minWaitTimeInSeconds,
55+
"max-wait-time", &maxWaitTimeInSeconds,
56+
"alert-duration", &alertDurationInSeconds,
57+
"log-level", &logLevel,
58+
);
59+
60+
enforce(minWaitTimeInSeconds <= maxWaitTimeInSeconds, "Make sure that `max-wait-time` is greater than `min-wait-time`.");
61+
62+
setLogLevel(logLevel);
63+
64+
executeAtRandomIntervals(
65+
Params(
66+
url: url,
67+
startTime: TimeOfDay.fromISOExtString(startTime),
68+
endTime: TimeOfDay.fromISOExtString(endTime),
69+
minWaitTime: minWaitTimeInSeconds.seconds(),
70+
maxWaitTime: maxWaitTimeInSeconds.seconds(),
71+
alertDuration: alertDurationInSeconds.seconds(),
72+
)
73+
);
74+
}
75+
catch (Exception e)
76+
{
77+
errorf("Exception: %s", e.message);
78+
return 1;
79+
}
80+
return 0;
81+
}
82+
83+
auto getRandomDuration(Duration min, Duration max) =>
84+
uniform(min.total!"seconds", max.total!"seconds").seconds;
85+
86+
void executeAtRandomIntervals(Params params)
87+
{
88+
with(params) while (true)
89+
{
90+
auto currentTime = Clock.currTime();
91+
auto currentTimeTOD = cast(TimeOfDay)currentTime;
92+
Duration randomDuration = getRandomDuration(minWaitTime, maxWaitTime);
93+
auto randomTime = currentTime + randomDuration;
94+
95+
tracef("The operating time is: [%s .. %s]", startTime, endTime);
96+
tracef("The next alarm will be activated in %s", randomTime);
97+
98+
Thread.sleep(randomDuration); // sleep till the request is ready to be posted.
99+
100+
if (currentTimeTOD >= startTime && currentTimeTOD <= endTime)
101+
{
102+
infof("Posting alert... ");
103+
postAlert(url, alertDuration);
104+
infof("Alert posted successfully.");
105+
}
106+
else
107+
{
108+
infof("This service is outside working hours. The operating time is: [%s .. %s]", startTime, endTime);
109+
}
110+
111+
Duration remainingTime = maxWaitTime - randomDuration;
112+
113+
tracef("The current interval's end will be at %s", (currentTime + remainingTime));
114+
Thread.sleep(remainingTime); // sleep till the cycle is over.
115+
}
116+
}
117+
118+
void postAlert(string alertManagerEndpoint, Duration alertDuration)
119+
{
120+
string url = alertManagerEndpoint ~ "/api/v2/alerts";
121+
122+
postJson(url, [
123+
Alert(
124+
startsAt: Clock.currTime.toUTC.toISOExtString(0),
125+
endsAt: (Clock.currTime + alertDuration).toUTC.toISOExtString(0),
126+
generatorURL: "http://localhost:9090",
127+
labels: [
128+
"alertname": "Random alert",
129+
"severity": "critical",
130+
"environment": "staging",
131+
"job": "test-monitoring",
132+
],
133+
annotations: Alert.Annotation(
134+
alert_type: "critical",
135+
title: "Write report",
136+
summary: "The alert was triggered at '%s'".format(Clock.currTime.toUTC)
137+
)
138+
)
139+
]);
140+
}
141+
142+
JSONValue postJson(T)(string url, in T value)
143+
{
144+
import std.net.curl : HTTP, post, HTTPStatusException;
145+
146+
auto jsonRequest = value
147+
.toJSON
148+
.toPrettyString(JSONOptions.doNotEscapeSlashes);
149+
150+
tracef("Sending request to '%s':\n%s", url, jsonRequest);
151+
152+
auto http = HTTP();
153+
http.addRequestHeader("Content-Type", "application/json");
154+
155+
auto response = post(url, jsonRequest, http);
156+
return response.parseJSON;
157+
}
158+
159+
void setLogLevel(LogLevel l)
160+
{
161+
import std.logger : globalLogLevel, sharedLog;
162+
globalLogLevel = l;
163+
(cast()sharedLog()).logLevel = l;
164+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
module utils.json;
2+
3+
import std.traits: isNumeric, isArray, isSomeChar, ForeachType, isBoolean;
4+
import std.json: JSONValue;
5+
import std.conv: to;
6+
import std.string: strip;
7+
import std.range: front;
8+
import std.algorithm: map;
9+
import std.array: array;
10+
import std.datetime: SysTime;
11+
import core.stdc.string: strlen;
12+
13+
JSONValue toJSON(T)(in T value, bool simplify = false)
14+
{
15+
static if (is(T == enum))
16+
{
17+
return JSONValue(value.enumToString);
18+
}
19+
else static if (is(T == bool) || is(T == string) || isSomeChar!T || isNumeric!T)
20+
return JSONValue(value);
21+
else static if ((isArray!T && isSomeChar!(ForeachType!T)) ) {
22+
return JSONValue(value.idup[0..(strlen(value.ptr)-1)]);
23+
}
24+
else static if (isArray!T)
25+
{
26+
if (simplify && value.length == 1)
27+
return value.front.toJSON(simplify);
28+
else if (simplify && isBoolean!(ForeachType!T) ) {
29+
static if (isBoolean!(ForeachType!T)) {
30+
return JSONValue((value.map!(a => a ? '1' : '0').array).to!string);
31+
}
32+
else {assert(0);}
33+
}
34+
else {
35+
JSONValue[] result;
36+
foreach (elem; value)
37+
result ~= elem.toJSON(simplify);
38+
return JSONValue(result);
39+
}
40+
}
41+
else static if (is(T == SysTime)) {
42+
return JSONValue(value.toISOExtString());
43+
}
44+
else static if (is(T == struct))
45+
{
46+
JSONValue[string] result;
47+
auto name = "";
48+
static foreach (idx, field; T.tupleof)
49+
{
50+
name = __traits(identifier, field).strip("_");
51+
result[name] = value.tupleof[idx].toJSON(simplify);
52+
}
53+
return JSONValue(result);
54+
}
55+
else static if (is(T == K[V], K, V))
56+
{
57+
JSONValue[string] result;
58+
foreach (key, field; value)
59+
{
60+
result[key] = field.toJSON(simplify);
61+
}
62+
return JSONValue(result);
63+
}
64+
else
65+
static assert(false, "Unsupported type: `" ~ __traits(identifier, T) ~ "`");
66+
}

0 commit comments

Comments
 (0)