Created
November 18, 2016 09:34
-
-
Save haya14busa/e983e3aac3c8932b5f5ffd674ee9038a to your computer and use it in GitHub Desktop.
mackerel-client-go: benchmark: decoding monitors JSON
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/monitors.go b/monitors.go | |
index c8a32aa..5d861b4 100644 | |
--- a/monitors.go | |
+++ b/monitors.go | |
@@ -2,8 +2,11 @@ package mackerel | |
import ( | |
"encoding/json" | |
+ "errors" | |
"fmt" | |
"net/http" | |
+ | |
+ "github.com/mitchellh/mapstructure" | |
) | |
/* | |
@@ -65,6 +68,134 @@ import ( | |
} | |
*/ | |
+// monitorI represents interface to which each monitor type must confirm to. | |
+// TODO(haya14busa): remove trailing `I` in the name after migrating interface. | |
+type monitorI interface { | |
+ // MonitorType() must return monitor type. | |
+ MonitorType() string | |
+} | |
+ | |
+const ( | |
+ monitorTypeConnectivity = "connectivity" | |
+ monitorTypeHostMeric = "host" | |
+ monitorTypeServiceMetric = "service" | |
+ monitorTypeExternalHTTP = "external" | |
+ monitorTypeExpression = "expression" | |
+) | |
+ | |
+// Ensure each monitor type conforms to the Monitor interface. | |
+var ( | |
+ _ monitorI = (*MonitorConnectivity)(nil) | |
+ _ monitorI = (*MonitorHostMetric)(nil) | |
+ _ monitorI = (*MonitorServiceMetric)(nil) | |
+ _ monitorI = (*MonitorExternalHTTP)(nil) | |
+ _ monitorI = (*MonitorExpression)(nil) | |
+) | |
+ | |
+// MonitorConnectivity represents connectivity monitor. | |
+type MonitorConnectivity struct { | |
+ ID string `json:"id,omitempty"` | |
+ Name string `json:"name,omitempty"` | |
+ Type string `json:"type,omitempty"` | |
+ IsMute bool `json:"isMute,omitempty"` | |
+ NotificationInterval uint64 `json:"notificationInterval,omitempty"` | |
+ | |
+ Scopes []string `json:"scopes,omitempty"` | |
+ ExcludeScopes []string `json:"excludeScopes,omitempty"` | |
+} | |
+ | |
+// MonitorType returns monitor type. | |
+func (m *MonitorConnectivity) MonitorType() string { | |
+ return monitorTypeConnectivity | |
+} | |
+ | |
+// MonitorHostMetric represents host metric monitor. | |
+type MonitorHostMetric struct { | |
+ ID string `json:"id,omitempty"` | |
+ Name string `json:"name,omitempty"` | |
+ Type string `json:"type,omitempty"` | |
+ IsMute bool `json:"isMute,omitempty"` | |
+ NotificationInterval uint64 `json:"notificationInterval,omitempty"` | |
+ | |
+ Metric string `json:"metric,omitempty"` | |
+ Operator string `json:"operator,omitempty"` | |
+ Warning float64 `json:"warning,omitempty"` | |
+ Critical float64 `json:"critical,omitempty"` | |
+ Duration uint64 `json:"duration,omitempty"` | |
+ | |
+ Scopes []string `json:"scopes,omitempty"` | |
+ ExcludeScopes []string `json:"excludeScopes,omitempty"` | |
+} | |
+ | |
+// MonitorType returns monitor type. | |
+func (m *MonitorHostMetric) MonitorType() string { | |
+ return monitorTypeHostMeric | |
+} | |
+ | |
+// MonitorServiceMetric represents service metric monitor. | |
+type MonitorServiceMetric struct { | |
+ ID string `json:"id,omitempty"` | |
+ Name string `json:"name,omitempty"` | |
+ Type string `json:"type,omitempty"` | |
+ IsMute bool `json:"isMute,omitempty"` | |
+ NotificationInterval uint64 `json:"notificationInterval,omitempty"` | |
+ | |
+ Service string `json:"service,omitempty"` | |
+ Metric string `json:"metric,omitempty"` | |
+ Operator string `json:"operator,omitempty"` | |
+ Warning float64 `json:"warning,omitempty"` | |
+ Critical float64 `json:"critical,omitempty"` | |
+ Duration uint64 `json:"duration,omitempty"` | |
+} | |
+ | |
+// MonitorType returns monitor type. | |
+func (m *MonitorServiceMetric) MonitorType() string { | |
+ return monitorTypeServiceMetric | |
+} | |
+ | |
+// MonitorExternalHTTP represents external HTTP monitor. | |
+type MonitorExternalHTTP struct { | |
+ ID string `json:"id,omitempty"` | |
+ Name string `json:"name,omitempty"` | |
+ Type string `json:"type,omitempty"` | |
+ IsMute bool `json:"isMute,omitempty"` | |
+ NotificationInterval uint64 `json:"notificationInterval,omitempty"` | |
+ | |
+ URL string `json:"url,omitempty"` | |
+ MaxCheckAttempts float64 `json:"maxCheckAttempts,omitempty"` | |
+ Service string `json:"service,omitempty"` | |
+ ResponseTimeCritical float64 `json:"responseTimeCritical,omitempty"` | |
+ ResponseTimeWarning float64 `json:"responseTimeWarning,omitempty"` | |
+ ResponseTimeDuration float64 `json:"responseTimeDuration,omitempty"` | |
+ ContainsString string `json:"containsString,omitempty"` | |
+ CertificationExpirationCritical uint64 `json:"certificationExpirationCritical,omitempty"` | |
+ CertificationExpirationWarning uint64 `json:"certificationExpirationWarning,omitempty"` | |
+} | |
+ | |
+// MonitorType returns monitor type. | |
+func (m *MonitorExternalHTTP) MonitorType() string { | |
+ return monitorTypeExternalHTTP | |
+} | |
+ | |
+// MonitorExpression represents expression monitor. | |
+type MonitorExpression struct { | |
+ ID string `json:"id,omitempty"` | |
+ Name string `json:"name,omitempty"` | |
+ Type string `json:"type,omitempty"` | |
+ IsMute bool `json:"isMute,omitempty"` | |
+ NotificationInterval uint64 `json:"notificationInterval,omitempty"` | |
+ | |
+ Expression string `json:"expression,omitempty"` | |
+ Operator string `json:"operator,omitempty"` | |
+ Warning float64 `json:"warning,omitempty"` | |
+ Critical float64 `json:"critical,omitempty"` | |
+} | |
+ | |
+// MonitorType returns monitor type. | |
+func (m *MonitorExpression) MonitorType() string { | |
+ return monitorTypeExpression | |
+} | |
+ | |
// Monitor information | |
type Monitor struct { | |
ID string `json:"id,omitempty"` | |
@@ -170,3 +301,62 @@ func (c *Client) DeleteMonitor(monitorID string) (*Monitor, error) { | |
} | |
return &data, nil | |
} | |
+ | |
+// decodeMonitorFromMap decodes map[string]interface{} to monitorI. | |
+func decodeMonitorFromMap(mmap map[string]interface{}) (monitorI, error) { | |
+ typ, ok := mmap["type"] | |
+ if !ok { | |
+ return nil, errors.New("`type` field not found") | |
+ } | |
+ var m monitorI | |
+ switch typ { | |
+ case monitorTypeConnectivity: | |
+ m = &MonitorConnectivity{} | |
+ case monitorTypeHostMeric: | |
+ m = &MonitorHostMetric{} | |
+ case monitorTypeServiceMetric: | |
+ m = &MonitorServiceMetric{} | |
+ case monitorTypeExternalHTTP: | |
+ m = &MonitorExternalHTTP{} | |
+ case monitorTypeExpression: | |
+ m = &MonitorExpression{} | |
+ } | |
+ c := &mapstructure.DecoderConfig{ | |
+ TagName: "json", | |
+ Result: m, | |
+ } | |
+ d, err := mapstructure.NewDecoder(c) | |
+ if err != nil { | |
+ return nil, err | |
+ } | |
+ if err := d.Decode(mmap); err != nil { | |
+ return nil, err | |
+ } | |
+ return m, nil | |
+} | |
+ | |
+func decodeMonitorFromRawMessage(rawmes []byte) (monitorI, error) { | |
+ var typeData struct { | |
+ Type string `json:"type"` | |
+ } | |
+ if err := json.Unmarshal(rawmes, &typeData); err != nil { | |
+ return nil, err | |
+ } | |
+ var m monitorI | |
+ switch typeData.Type { | |
+ case monitorTypeConnectivity: | |
+ m = &MonitorConnectivity{} | |
+ case monitorTypeHostMeric: | |
+ m = &MonitorHostMetric{} | |
+ case monitorTypeServiceMetric: | |
+ m = &MonitorServiceMetric{} | |
+ case monitorTypeExternalHTTP: | |
+ m = &MonitorExternalHTTP{} | |
+ case monitorTypeExpression: | |
+ m = &MonitorExpression{} | |
+ } | |
+ if err := json.Unmarshal(rawmes, m); err != nil { | |
+ return nil, err | |
+ } | |
+ return m, nil | |
+} | |
diff --git a/monitors_test.go b/monitors_test.go | |
index b1eef7a..2792889 100644 | |
--- a/monitors_test.go | |
+++ b/monitors_test.go | |
@@ -5,7 +5,11 @@ import ( | |
"fmt" | |
"net/http" | |
"net/http/httptest" | |
+ "reflect" | |
+ "strings" | |
"testing" | |
+ | |
+ "github.com/kylelemons/godebug/pretty" | |
) | |
func TestFindMonitors(t *testing.T) { | |
@@ -105,3 +109,192 @@ func TestFindMonitors(t *testing.T) { | |
t.Error("request sends json including expression but: ", monitors[2]) | |
} | |
} | |
+ | |
+const monitorsjson = ` | |
+{ | |
+ "monitors": [ | |
+ { | |
+ "id": "2cSZzK3XfmA", | |
+ "type": "connectivity", | |
+ "scopes": [], | |
+ "excludeScopes": [] | |
+ }, | |
+ { | |
+ "id" : "2cSZzK3XfmB", | |
+ "type": "host", | |
+ "name": "disk.aa-00.writes.delta", | |
+ "duration": 3, | |
+ "metric": "disk.aa-00.writes.delta", | |
+ "operator": ">", | |
+ "warning": 20000.0, | |
+ "critical": 400000.0, | |
+ "scopes": [ | |
+ "Hatena-Blog" | |
+ ], | |
+ "excludeScopes": [ | |
+ "Hatena-Bookmark: db-master" | |
+ ] | |
+ }, | |
+ { | |
+ "id" : "2cSZzK3XfmC", | |
+ "type": "service", | |
+ "name": "Hatena-Blog - access_num.4xx_count", | |
+ "service": "Hatena-Blog", | |
+ "duration": 1, | |
+ "metric": "access_num.4xx_count", | |
+ "operator": ">", | |
+ "warning": 50.0, | |
+ "critical": 100.0, | |
+ "notificationInterval": 60 | |
+ }, | |
+ { | |
+ "id" : "2cSZzK3XfmD", | |
+ "type": "external", | |
+ "name": "example.com", | |
+ "url": "http://www.example.com", | |
+ "service": "Hatena-Blog", | |
+ "headers": [{"name":"Cache-Control", "value":"no-cache"}] | |
+ }, | |
+ { | |
+ "id" : "2cSZzK3XfmE", | |
+ "type": "expression", | |
+ "name": "role average", | |
+ "expression": "avg(roleSlots(\"server:role\",\"loadavg5\"))", | |
+ "operator": ">", | |
+ "warning": 5.0, | |
+ "critical": 10.0, | |
+ "notificationInterval": 60 | |
+ } | |
+ ] | |
+} | |
+` | |
+ | |
+var wantMonitors = []monitorI{ | |
+ &MonitorConnectivity{ | |
+ ID: "2cSZzK3XfmA", | |
+ Name: "", | |
+ Type: "connectivity", | |
+ IsMute: false, | |
+ NotificationInterval: 0x0000000000000000, | |
+ Scopes: []string{}, | |
+ ExcludeScopes: []string{}, | |
+ }, | |
+ &MonitorHostMetric{ | |
+ ID: "2cSZzK3XfmB", | |
+ Name: "disk.aa-00.writes.delta", | |
+ Type: "host", | |
+ IsMute: false, | |
+ NotificationInterval: 0x0000000000000000, | |
+ Metric: "disk.aa-00.writes.delta", | |
+ Operator: ">", | |
+ Warning: 20000.000000, | |
+ Critical: 400000.000000, | |
+ Duration: 0x0000000000000003, | |
+ Scopes: []string{ | |
+ "Hatena-Blog", | |
+ }, | |
+ ExcludeScopes: []string{ | |
+ "Hatena-Bookmark: db-master", | |
+ }, | |
+ }, | |
+ &MonitorServiceMetric{ | |
+ ID: "2cSZzK3XfmC", | |
+ Name: "Hatena-Blog - access_num.4xx_count", | |
+ Type: "service", | |
+ IsMute: false, | |
+ NotificationInterval: 0x000000000000003c, | |
+ Service: "Hatena-Blog", | |
+ Metric: "access_num.4xx_count", | |
+ Operator: ">", | |
+ Warning: 50.000000, | |
+ Critical: 100.000000, | |
+ Duration: 0x0000000000000001, | |
+ }, | |
+ &MonitorExternalHTTP{ | |
+ ID: "2cSZzK3XfmD", | |
+ Name: "example.com", | |
+ Type: "external", | |
+ IsMute: false, | |
+ NotificationInterval: 0x0000000000000000, | |
+ URL: "http://www.example.com", | |
+ MaxCheckAttempts: 0.000000, | |
+ Service: "Hatena-Blog", | |
+ ResponseTimeCritical: 0.000000, | |
+ ResponseTimeWarning: 0.000000, | |
+ ResponseTimeDuration: 0.000000, | |
+ ContainsString: "", | |
+ CertificationExpirationCritical: 0x0000000000000000, | |
+ CertificationExpirationWarning: 0x0000000000000000, | |
+ }, | |
+ &MonitorExpression{ | |
+ ID: "2cSZzK3XfmE", | |
+ Name: "role average", | |
+ Type: "expression", | |
+ IsMute: false, | |
+ NotificationInterval: 0x000000000000003c, | |
+ Expression: "avg(roleSlots(\"server:role\",\"loadavg5\"))", | |
+ Operator: ">", | |
+ Warning: 5.000000, | |
+ Critical: 10.000000, | |
+ }, | |
+} | |
+ | |
+func TestMonitor_JSON_mapstructure(t *testing.T) { | |
+ if got := decodeMonitorsJSONByMapstructure(t); !reflect.DeepEqual(got, wantMonitors) { | |
+ t.Errorf("fail to get correct data: diff: (-got +want)\n%v", pretty.Compare(got, wantMonitors)) | |
+ } | |
+} | |
+ | |
+func TestMonitor_JSON_rawmessage(t *testing.T) { | |
+ if got := decodeMonitorsJSONByRawMessage(t); !reflect.DeepEqual(got, wantMonitors) { | |
+ t.Errorf("fail to get correct data: diff: (-got +want)\n%v", pretty.Compare(got, wantMonitors)) | |
+ } | |
+} | |
+ | |
+func BenchmarkMonitor_JSON_mapstructure(b *testing.B) { | |
+ for i := 0; i < b.N; i++ { | |
+ decodeMonitorsJSONByMapstructure(b) | |
+ } | |
+} | |
+ | |
+func BenchmarkMonitor_JSON_rawmessage(b *testing.B) { | |
+ for i := 0; i < b.N; i++ { | |
+ decodeMonitorsJSONByRawMessage(b) | |
+ } | |
+} | |
+ | |
+func decodeMonitorsJSONByMapstructure(t testing.TB) []monitorI { | |
+ var data struct { | |
+ Monitors []map[string]interface{} `json:"monitors"` | |
+ } | |
+ if err := json.NewDecoder(strings.NewReader(monitorsjson)).Decode(&data); err != nil { | |
+ t.Error(err) | |
+ } | |
+ ms := make([]monitorI, 0, len(data.Monitors)) | |
+ for _, mmap := range data.Monitors { | |
+ m, err := decodeMonitorFromMap(mmap) | |
+ if err != nil { | |
+ t.Error(err) | |
+ } | |
+ ms = append(ms, m) | |
+ } | |
+ return ms | |
+} | |
+ | |
+func decodeMonitorsJSONByRawMessage(t testing.TB) []monitorI { | |
+ var data struct { | |
+ Monitors []json.RawMessage `json:"monitors"` | |
+ } | |
+ if err := json.NewDecoder(strings.NewReader(monitorsjson)).Decode(&data); err != nil { | |
+ t.Error(err) | |
+ } | |
+ ms := make([]monitorI, 0, len(data.Monitors)) | |
+ for _, rawmes := range data.Monitors { | |
+ m, err := decodeMonitorFromRawMessage(rawmes) | |
+ if err != nil { | |
+ t.Error(err) | |
+ } | |
+ ms = append(ms, m) | |
+ } | |
+ return ms | |
+} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment