diff --git a/examples/plugins/iowait/main.go b/examples/plugins/iowait/main.go index a77b899fb..486d38a33 100644 --- a/examples/plugins/iowait/main.go +++ b/examples/plugins/iowait/main.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "flag" "fmt" "log" @@ -52,6 +53,7 @@ func main() { plugin := &Plugin{HostID: *hostID} http.HandleFunc("/report", plugin.Report) + http.HandleFunc("/control", plugin.Control) if err := http.Serve(listener, nil); err != nil { log.Printf("error: %v", err) } @@ -59,63 +61,180 @@ func main() { // Plugin groups the methods a plugin needs type Plugin struct { - HostID string + HostID string + iowaitMode bool } // Report is called by scope when a new report is needed. It is part of the // "reporter" interface, which all plugins must implement. func (p *Plugin) Report(w http.ResponseWriter, r *http.Request) { log.Println(r.URL.String()) - now := time.Now() - nowISO := now.Format(time.RFC3339) - value, err := iowait() + metric, metricTemplate, err := p.metricsSnippets() if err != nil { log.Printf("error: %v", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - fmt.Fprintf(w, `{ + topologyControl, nodeControl := p.controlsSnippets() + rpt := fmt.Sprintf(`{ "Host": { "nodes": { %q: { - "metrics": { - "iowait": { - "samples": [ {"date": %q, "value": %f} ], - "min": 0, - "max": 100 - } - } + "metrics": { %s }, + "controls": { %s } } }, - "metric_templates": { - "iowait": { - "id": "iowait", - "label": "IO Wait", - "format": "percent", - "priority": 0.1 - } - } + "metric_templates": { %s }, + "controls": { %s } }, "Plugins": [ { "id": "iowait", "label": "iowait", "description": "Adds a graph of CPU IO Wait to hosts", - "interfaces": ["reporter"], + "interfaces": ["reporter", "controller"], "api_version": "1" } ] - }`, p.HostID+";", nowISO, value) + }`, p.getTopologyHost(), metric, nodeControl, metricTemplate, topologyControl) + fmt.Fprintf(w, "%s", rpt) +} + +// Request is just a trimmed down xfer.Request +type Request struct { + NodeID string + Control string +} + +// Control is called by scope when a control is activated. It is part +// of the "controller" interface. +func (p *Plugin) Control(w http.ResponseWriter, r *http.Request) { + log.Println(r.URL.String()) + xreq := Request{} + err := json.NewDecoder(r.Body).Decode(&xreq) if err != nil { - log.Printf("error: %v", err) + log.Printf("Bad request: %v", err) + w.WriteHeader(http.StatusBadRequest) + return } + thisNodeID := p.getTopologyHost() + if xreq.NodeID != thisNodeID { + log.Printf("Bad nodeID, expected %q, got %q", thisNodeID, xreq.NodeID) + w.WriteHeader(http.StatusBadRequest) + return + } + expectedControlID, _, _ := p.controlDetails() + if expectedControlID != xreq.Control { + log.Printf("Bad control, expected %q, got %q", expectedControlID, xreq.Control) + w.WriteHeader(http.StatusBadRequest) + return + } + p.iowaitMode = !p.iowaitMode + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, "{}") +} + +func (p *Plugin) getTopologyHost() string { + return fmt.Sprintf("%s;", p.HostID) +} + +// Get the metrics and metric_templates JSON snippets +func (p *Plugin) metricsSnippets() (string, string, error) { + id, name := p.metricIDAndName() + value, err := p.metricValue() + if err != nil { + return "", "", err + } + nowISO := rfcNow() + metric := fmt.Sprintf(` + %q: { + "samples": [ {"date": %q, "value": %f} ], + "min": 0, + "max": 100 + } +`, id, nowISO, value) + metricTemplate := fmt.Sprintf(` + %q: { + "id": %q, + "label": %q, + "format": "percent", + "priority": 0.1 + } +`, id, id, name) + return metric, metricTemplate, nil +} + +// Get the topology controls and node's controls JSON snippet +func (p *Plugin) controlsSnippets() (string, string) { + id, human, icon := p.controlDetails() + nowISO := rfcNow() + topologyControl := fmt.Sprintf(` + %q: { + "id": %q, + "human": %q, + "icon": %q, + "rank": 1 + } +`, id, id, human, icon) + nodeControl := fmt.Sprintf(` + "timestamp": %q, + "controls": [%q] +`, nowISO, id) + return topologyControl, nodeControl +} + +func rfcNow() string { + now := time.Now() + return now.Format(time.RFC3339) +} + +func (p *Plugin) metricIDAndName() (string, string) { + if p.iowaitMode { + return "iowait", "IO Wait" + } + return "idle", "Idle" +} + +func (p *Plugin) metricValue() (float64, error) { + if p.iowaitMode { + return iowait() + } + return idle() +} + +func (p *Plugin) controlDetails() (string, string, string) { + if p.iowaitMode { + return "switchToIdle", "Switch to idle", "fa-beer" + } + return "switchToIOWait", "Switch to IO wait", "fa-hourglass" } // Get the latest iowait value func iowait() (float64, error) { + return iostatValue(3) +} + +func idle() (float64, error) { + return iostatValue(5) +} + +func iostatValue(idx int) (float64, error) { + values, err := iostat() + if err != nil { + return 0, err + } + if idx >= len(values) { + return 0, fmt.Errorf("invalid iostat field index %d", idx) + } + + return strconv.ParseFloat(values[idx], 64) +} + +// Get the latest iostat values +func iostat() ([]string, error) { out, err := exec.Command("iostat", "-c").Output() if err != nil { - return 0, fmt.Errorf("iowait: %v", err) + return nil, fmt.Errorf("iowait: %v", err) } // Linux 4.2.0-25-generic (a109563eab38) 04/01/16 _x86_64_(4 CPU) @@ -124,13 +243,12 @@ func iowait() (float64, error) { // 2.37 0.00 1.58 0.01 0.00 96.04 lines := strings.Split(string(out), "\n") if len(lines) < 4 { - return 0, fmt.Errorf("iowait: unexpected output: %q", out) + return nil, fmt.Errorf("iowait: unexpected output: %q", out) } values := strings.Fields(lines[3]) if len(values) != 6 { - return 0, fmt.Errorf("iowait: unexpected output: %q", out) + return nil, fmt.Errorf("iowait: unexpected output: %q", out) } - - return strconv.ParseFloat(values[3], 64) + return values, nil }