Skip to content

Commit 94e4c67

Browse files
committed
feat(packages): Add random-alerts package
1 parent b9238be commit 94e4c67

File tree

8 files changed

+508
-0
lines changed

8 files changed

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

0 commit comments

Comments
 (0)