Skip to content

Instantly share code, notes, and snippets.

@haya14busa
Created November 18, 2016 09:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save haya14busa/e983e3aac3c8932b5f5ffd674ee9038a to your computer and use it in GitHub Desktop.
Save haya14busa/e983e3aac3c8932b5f5ffd674ee9038a to your computer and use it in GitHub Desktop.
mackerel-client-go: benchmark: decoding monitors JSON
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