Browse Source

Add support for iterating over external data

Brendan Abolivier 7 years ago
parent
commit
3fc703852c
Signed by: Brendan Abolivier <contact@brendanabolivier.com> GPG key ID: 8EF1500759F70623

+ 4
- 2
src/metrics-alerting/alert/alert.go View File

4
 	"fmt"
4
 	"fmt"
5
 
5
 
6
 	"metrics-alerting/config"
6
 	"metrics-alerting/config"
7
+	"metrics-alerting/script_data"
7
 
8
 
8
 	"gopkg.in/gomail.v2"
9
 	"gopkg.in/gomail.v2"
9
 )
10
 )
17
 	script config.Script,
18
 	script config.Script,
18
 	result interface{},
19
 	result interface{},
19
 	labels map[string]string,
20
 	labels map[string]string,
21
+	data script_data.Data,
20
 ) error {
22
 ) error {
21
 	switch script.Action {
23
 	switch script.Action {
22
 	case "http":
24
 	case "http":
23
-		return a.alertHttp(script, result, labels)
25
+		return a.alertHttp(script, result, labels, data)
24
 	case "email":
26
 	case "email":
25
-		return a.alertEmail(script, result, labels)
27
+		return a.alertEmail(script, result, labels, data)
26
 	default:
28
 	default:
27
 		return fmt.Errorf("invalid action type: %s", script.Action)
29
 		return fmt.Errorf("invalid action type: %s", script.Action)
28
 	}
30
 	}

+ 19
- 7
src/metrics-alerting/alert/email.go View File

6
 	"strings"
6
 	"strings"
7
 
7
 
8
 	"metrics-alerting/config"
8
 	"metrics-alerting/config"
9
+	"metrics-alerting/script_data"
9
 
10
 
10
 	"gopkg.in/gomail.v2"
11
 	"gopkg.in/gomail.v2"
11
 )
12
 )
14
 	script config.Script,
15
 	script config.Script,
15
 	result interface{},
16
 	result interface{},
16
 	labels map[string]string,
17
 	labels map[string]string,
18
+	data script_data.Data,
17
 ) error {
19
 ) error {
18
-	formatNumber := "Script %s just exceeded its threshold of %.2f and now returns %f"
19
-	formatBool := "Test for script %s and returned false instead of true"
20
+	formatNumber := "Script \"%s\" just exceeded its threshold of %.2f and now returns %f"
21
+	formatBool := "Test for script \"%s\" failed and returned false instead of true"
20
 
22
 
21
 	var body, subject string
23
 	var body, subject string
22
 	switch script.Type {
24
 	switch script.Type {
23
 	case "number", "series":
25
 	case "number", "series":
24
 		subject = fmt.Sprintf(
26
 		subject = fmt.Sprintf(
25
-			"Threshold exceeded for script %s %s", script.Key,
26
-			getIdentifyingLabels(script, labels),
27
+			"Threshold exceeded for script \"%s\" %s%s", script.Key,
28
+			getIdentifyingLabels(script, labels), getScriptData(data),
27
 		)
29
 		)
28
 		body = fmt.Sprintf(
30
 		body = fmt.Sprintf(
29
 			formatNumber, script.Key, script.Threshold, result.(float64),
31
 			formatNumber, script.Key, script.Threshold, result.(float64),
30
 		)
32
 		)
31
 	case "bool":
33
 	case "bool":
32
 		subject = fmt.Sprintf(
34
 		subject = fmt.Sprintf(
33
-			"Test for script %s failed %s", script.Key,
34
-			getIdentifyingLabels(script, labels),
35
+			"Test for script \"%s\" failed %s%s", script.Key,
36
+			getIdentifyingLabels(script, labels), getScriptData(data),
35
 		)
37
 		)
36
 		body = fmt.Sprintf(formatBool, script.Key)
38
 		body = fmt.Sprintf(formatBool, script.Key)
37
 	}
39
 	}
65
 
67
 
66
 	identifyingLabels := make(map[string]string)
68
 	identifyingLabels := make(map[string]string)
67
 	for _, label := range script.IdentifyingLabels {
69
 	for _, label := range script.IdentifyingLabels {
68
-		identifyingLabels[label] = labels[label]
70
+		if len(labels[label]) > 0 {
71
+			identifyingLabels[label] = labels[label]
72
+		}
69
 	}
73
 	}
70
 
74
 
71
 	labelsAsStrs := []string{}
75
 	labelsAsStrs := []string{}
77
 
81
 
78
 	return "(" + strings.Join(labelsAsStrs, ", ") + ")"
82
 	return "(" + strings.Join(labelsAsStrs, ", ") + ")"
79
 }
83
 }
84
+
85
+func getScriptData(data script_data.Data) string {
86
+	if len(data.Key) == 0 {
87
+		return ""
88
+	}
89
+
90
+	return "(" + data.Key + "=" + data.Value + ")"
91
+}

+ 7
- 0
src/metrics-alerting/alert/http.go View File

8
 	"strconv"
8
 	"strconv"
9
 
9
 
10
 	"metrics-alerting/config"
10
 	"metrics-alerting/config"
11
+	"metrics-alerting/script_data"
11
 )
12
 )
12
 
13
 
13
 type alertBody struct {
14
 type alertBody struct {
14
 	Key    string            `json:"scriptKey"`
15
 	Key    string            `json:"scriptKey"`
15
 	Value  string            `json:"value"`
16
 	Value  string            `json:"value"`
16
 	Labels map[string]string `json:"labels"`
17
 	Labels map[string]string `json:"labels"`
18
+	Data   map[string]string `json:"data"`
17
 }
19
 }
18
 
20
 
19
 func (a *Alerter) alertHttp(
21
 func (a *Alerter) alertHttp(
20
 	script config.Script,
22
 	script config.Script,
21
 	result interface{},
23
 	result interface{},
22
 	labels map[string]string,
24
 	labels map[string]string,
25
+	data script_data.Data,
23
 ) error {
26
 ) error {
24
 	var value string
27
 	var value string
25
 	switch script.Type {
28
 	switch script.Type {
29
 		value = strconv.FormatBool(result.(bool))
32
 		value = strconv.FormatBool(result.(bool))
30
 	}
33
 	}
31
 
34
 
35
+	returnData := make(map[string]string)
36
+	returnData[data.Key] = data.Value
37
+
32
 	alert := alertBody{
38
 	alert := alertBody{
33
 		Key:    script.Key,
39
 		Key:    script.Key,
34
 		Value:  value,
40
 		Value:  value,
35
 		Labels: labels,
41
 		Labels: labels,
42
+		Data:   returnData,
36
 	}
43
 	}
37
 
44
 
38
 	body, err := json.Marshal(alert)
45
 	body, err := json.Marshal(alert)

+ 62
- 2
src/metrics-alerting/config/config.go View File

1
 package config
1
 package config
2
 
2
 
3
 import (
3
 import (
4
+	"bufio"
5
+	"io"
4
 	"io/ioutil"
6
 	"io/ioutil"
7
+	"os"
5
 
8
 
6
 	"gopkg.in/yaml.v2"
9
 	"gopkg.in/yaml.v2"
7
 )
10
 )
24
 	Password string `yaml:"password"`
27
 	Password string `yaml:"password"`
25
 }
28
 }
26
 
29
 
30
+type ScriptDataSource struct {
31
+	// Data to load from a file containing the content for the slide, one
32
+	// element per line
33
+	FromFile map[string]string `yaml:"from_file,omitempty"`
34
+	// Plain data
35
+	Plain map[string][]string `yaml:"plain,omitempty"`
36
+}
37
+
27
 type Script struct {
38
 type Script struct {
28
 	// An identifying key for the script
39
 	// An identifying key for the script
29
 	Key string `yaml:"key"`
40
 	Key string `yaml:"key"`
41
 	// The labels that will be mentioned in the email subject, only required if
52
 	// The labels that will be mentioned in the email subject, only required if
42
 	// the action is "email"
53
 	// the action is "email"
43
 	IdentifyingLabels []string `yaml:"identifying_labels,omitempty"`
54
 	IdentifyingLabels []string `yaml:"identifying_labels,omitempty"`
55
+	// Data to use in the script
56
+	DataSource ScriptDataSource `yaml:"script_data,omitempty"`
57
+	// Loaded data
58
+	ScriptData map[string][]string
44
 }
59
 }
45
 
60
 
46
 type Config struct {
61
 type Config struct {
55
 	Scripts []Script `yaml:"scripts"`
70
 	Scripts []Script `yaml:"scripts"`
56
 }
71
 }
57
 
72
 
58
-func Load(filePath string) (cfg Config, err error) {
73
+func (cfg *Config) Load(filePath string) (err error) {
59
 	content, err := ioutil.ReadFile(filePath)
74
 	content, err := ioutil.ReadFile(filePath)
60
 	if err != nil {
75
 	if err != nil {
61
 		return
76
 		return
62
 	}
77
 	}
63
 
78
 
64
 	err = yaml.Unmarshal(content, &cfg)
79
 	err = yaml.Unmarshal(content, &cfg)
65
-	return
80
+	if err != nil {
81
+		return
82
+	}
83
+
84
+	return cfg.loadData()
85
+}
86
+
87
+func (cfg *Config) loadData() error {
88
+	var line string
89
+	var l []byte
90
+	var isPrefix bool
91
+	for i, script := range cfg.Scripts {
92
+		script.ScriptData = make(map[string][]string)
93
+		for key, fileName := range script.DataSource.FromFile {
94
+			fp, err := os.Open(fileName)
95
+			if err != nil {
96
+				return err
97
+			}
98
+			reader := bufio.NewReader(fp)
99
+
100
+			for true {
101
+				isPrefix = true
102
+				line = ""
103
+				for isPrefix {
104
+					l, isPrefix, err = reader.ReadLine()
105
+					if err != nil && err != io.EOF {
106
+						return err
107
+					}
108
+					line = line + string(l)
109
+				}
110
+
111
+				if err == io.EOF {
112
+					break
113
+				}
114
+
115
+				script.ScriptData[key] = append(script.ScriptData[key], line)
116
+			}
117
+		}
118
+		for key, slice := range script.DataSource.Plain {
119
+			script.ScriptData[key] = slice
120
+		}
121
+
122
+		cfg.Scripts[i] = script
123
+	}
124
+
125
+	return nil
66
 }
126
 }

+ 5
- 18
src/metrics-alerting/main.go View File

2
 
2
 
3
 import (
3
 import (
4
 	"flag"
4
 	"flag"
5
-	"fmt"
6
 
5
 
7
 	"metrics-alerting/alert"
6
 	"metrics-alerting/alert"
8
 	"metrics-alerting/config"
7
 	"metrics-alerting/config"
20
 func main() {
19
 func main() {
21
 	flag.Parse()
20
 	flag.Parse()
22
 
21
 
23
-	cfg, _ := config.Load(*configPath)
22
+	cfg := config.Config{}
23
+	if err := cfg.Load(*configPath); err != nil {
24
+		logrus.Panic(err)
25
+	}
24
 	client := warp10.Warp10Client{
26
 	client := warp10.Warp10Client{
25
 		ExecEndpoint: cfg.Warp10Exec,
27
 		ExecEndpoint: cfg.Warp10Exec,
26
 		ReadToken:    cfg.ReadToken,
28
 		ReadToken:    cfg.ReadToken,
35
 	}
37
 	}
36
 
38
 
37
 	for _, script := range cfg.Scripts {
39
 	for _, script := range cfg.Scripts {
38
-		var err error
39
-		switch script.Type {
40
-		case "number":
41
-			err = process.ProcessNumber(client, script, alerter)
42
-			break
43
-		case "bool":
44
-			err = process.ProcessBool(client, script, alerter)
45
-			break
46
-		case "series":
47
-			err = process.ProcessSeries(client, script, alerter)
48
-			break
49
-		default:
50
-			err = fmt.Errorf("invalid return type: %s", script.Type)
51
-		}
52
-
53
-		if err != nil {
40
+		if err := process.Process(client, script, alerter); err != nil {
54
 			logrus.Error(err)
41
 			logrus.Error(err)
55
 		}
42
 		}
56
 	}
43
 	}

+ 64
- 7
src/metrics-alerting/process/process.go View File

1
 package process
1
 package process
2
 
2
 
3
 import (
3
 import (
4
+	"fmt"
5
+	"regexp"
4
 	"time"
6
 	"time"
5
 
7
 
6
 	"metrics-alerting/alert"
8
 	"metrics-alerting/alert"
7
 	"metrics-alerting/config"
9
 	"metrics-alerting/config"
10
+	"metrics-alerting/script_data"
8
 	"metrics-alerting/warp10"
11
 	"metrics-alerting/warp10"
9
 )
12
 )
10
 
13
 
11
-func ProcessNumber(
14
+func Process(
12
 	client warp10.Warp10Client,
15
 	client warp10.Warp10Client,
13
 	script config.Script,
16
 	script config.Script,
14
 	alerter alert.Alerter,
17
 	alerter alert.Alerter,
15
 ) error {
18
 ) error {
19
+	var scriptData script_data.Data
20
+	// TODO: Process more than one dataset
21
+	for key, data := range script.ScriptData {
22
+		scriptData.Key = key
23
+		r, err := regexp.Compile("`" + key + "`")
24
+		if err != nil {
25
+			return err
26
+		}
27
+		match := r.Find([]byte(script.Script))
28
+		if len(match) == 0 {
29
+			return fmt.Errorf("no variable named %s in script %s", key, script.Key)
30
+		}
31
+
32
+		origScript := script.Script
33
+
34
+		for _, el := range data {
35
+			scriptData.Value = el
36
+			filledScript := r.ReplaceAll([]byte(origScript), []byte(el))
37
+			script.Script = string(filledScript)
38
+			if err = dispatchType(client, script, alerter, scriptData); err != nil {
39
+				return err
40
+			}
41
+		}
42
+	}
43
+
44
+	return nil
45
+}
46
+
47
+func dispatchType(
48
+	client warp10.Warp10Client,
49
+	script config.Script,
50
+	alerter alert.Alerter,
51
+	data script_data.Data,
52
+) error {
53
+	switch script.Type {
54
+	case "number":
55
+		return processNumber(client, script, alerter, data)
56
+	case "bool":
57
+		return processBool(client, script, alerter, data)
58
+	case "series":
59
+		return processSeries(client, script, alerter, data)
60
+	}
61
+	return fmt.Errorf("invalid return type: %s", script.Type)
62
+}
63
+
64
+func processNumber(
65
+	client warp10.Warp10Client,
66
+	script config.Script,
67
+	alerter alert.Alerter,
68
+	data script_data.Data,
69
+) error {
16
 	value, err := client.ReadNumber(script.Script)
70
 	value, err := client.ReadNumber(script.Script)
17
 	if err != nil {
71
 	if err != nil {
18
 		return err
72
 		return err
19
 	}
73
 	}
20
 
74
 
21
-	return processFloat(value, script, alerter, nil)
75
+	return processFloat(value, script, alerter, nil, data)
22
 }
76
 }
23
 
77
 
24
-func ProcessBool(
78
+func processBool(
25
 	client warp10.Warp10Client,
79
 	client warp10.Warp10Client,
26
 	script config.Script,
80
 	script config.Script,
27
 	alerter alert.Alerter,
81
 	alerter alert.Alerter,
82
+	data script_data.Data,
28
 ) error {
83
 ) error {
29
 	value, err := client.ReadBool(script.Script)
84
 	value, err := client.ReadBool(script.Script)
30
 	if err != nil {
85
 	if err != nil {
35
 		return nil
90
 		return nil
36
 	}
91
 	}
37
 
92
 
38
-	return alerter.Alert(script, value, nil)
93
+	return alerter.Alert(script, value, nil, data)
39
 }
94
 }
40
 
95
 
41
-func ProcessSeries(
96
+func processSeries(
42
 	client warp10.Warp10Client,
97
 	client warp10.Warp10Client,
43
 	script config.Script,
98
 	script config.Script,
44
 	alerter alert.Alerter,
99
 	alerter alert.Alerter,
100
+	data script_data.Data,
45
 ) error {
101
 ) error {
46
 	series, err := client.ReadSeriesOfNumbers(script.Script)
102
 	series, err := client.ReadSeriesOfNumbers(script.Script)
47
 
103
 
63
 		// to find when the situation began so we can add info about time in the
119
 		// to find when the situation began so we can add info about time in the
64
 		// alert.
120
 		// alert.
65
 		if err = processFloat(
121
 		if err = processFloat(
66
-			serie.Datapoints[0][1], script, alerter, serie.Labels,
122
+			serie.Datapoints[0][1], script, alerter, serie.Labels, data,
67
 		); err != nil {
123
 		); err != nil {
68
 			return err
124
 			return err
69
 		}
125
 		}
77
 	script config.Script,
133
 	script config.Script,
78
 	alerter alert.Alerter,
134
 	alerter alert.Alerter,
79
 	labels map[string]string,
135
 	labels map[string]string,
136
+	data script_data.Data,
80
 ) error {
137
 ) error {
81
 	if value < script.Threshold {
138
 	if value < script.Threshold {
82
 		// Nothing to alert about
139
 		// Nothing to alert about
83
 		return nil
140
 		return nil
84
 	}
141
 	}
85
 
142
 
86
-	return alerter.Alert(script, value, labels)
143
+	return alerter.Alert(script, value, labels, data)
87
 }
144
 }
88
 
145
 
89
 func isRecentEnough(datapoint []float64) bool {
146
 func isRecentEnough(datapoint []float64) bool {

+ 6
- 0
src/metrics-alerting/script_data/script_data.go View File

1
+package script_data
2
+
3
+type Data struct {
4
+	Key   string
5
+	Value string
6
+}