Adding support for plugins, with basic example of iowait, and ebpf

Squash of:
* Include plugins in the report
* show plugin list in the UI
* moving metric and metadata templates into the probe reports
* update js for prime -> priority
* added retry to plugin handshake
* added iowait plugin
* review feedback
* plugin documentation
This commit is contained in:
Paul Bellamy
2016-04-12 17:22:14 +01:00
parent f899f4451b
commit 7632e0b3c5
50 changed files with 2168 additions and 537 deletions
+57
View File
@@ -0,0 +1,57 @@
# Scope Plugins
## <a id="protocol"></a>Protocol
All plugins should listen for HTTP connections on a unix socket in the
`/var/run/scope/plugins` directory. The scope probe will recursively scan that
directory every 5 seconds, to look for sockets being added (or removed). It is
also valid to put the plugin unix socket in a sub-directory, in case you want
to apply some permissions, or store other information with the socket.
When a new plugin is detected, the scope probe will conduct a basic
[Handshake](#handshake) by requesting `GET /`.
All plugin endpoints are expected to respond within 500ms, and respond in the JSON format.
### <a id="handshake"></a>Handshake
When the scope probe discovers a new plugin unix socket it needs to know some
information about the plugin. To learn this it will make a GET request for the
`/` endpoint.
An example response is:
```json
{
"name": "iowait",
"description": "Adds a graph of CPU IO Wait to hosts",
"interfaces": []string{"reporter"},
"api_version": "1",
}
```
The fields are:
* `name` is used to check for duplicate plugins, and displayed in the UI
* `description` is displayed in the UI
* `interfaces` tells the scope probe which endpoints this plugin supports
* `api_version` is used to ensure both the plugin and the scope probe can speak to each other
### <a id="interfaces"></a>Interfaces
Currently the only interface a plugin can fulfill is `reporter`.
#### <a id="reporter"></a>Reporter
The `reporter` interface allows a plugin to add information into the probe report. This could include more nodes, or new fields on existing nodes.
Endpoints:
* GET /report
This endpoint should return a scope probe-style report. For an example of the
datastructure see `/api/report` on any scope instance. At the moment the plugin
is limited to adding nodes or fields to existing topologies (Endpoint, Process,
Container, etc), along with `metadata_templates` and `metric_templates` to
display more information. For an example of adding a metric to the hosts, see
[the example iowait
plugin.](https://github.com/weaveworks/scope/tree/master/example/plugins/iowait)
+1
View File
@@ -0,0 +1 @@
iowait
+6
View File
@@ -0,0 +1,6 @@
FROM alpine:3.3
MAINTAINER Weaveworks Inc <help@weave.works>
LABEL works.weave.role=system
COPY ./iowait /usr/bin/iowait
RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2
ENTRYPOINT ["/usr/bin/iowait"]
+19
View File
@@ -0,0 +1,19 @@
.PHONY: run clean
EXE=iowait
IMAGE=weavescope-iowait-plugin
UPTODATE=.$(EXE).uptodate
run: $(UPTODATE)
docker run --rm -it --privileged -v /var/run/scope/plugins:/var/run/scope/plugins --name $(IMAGE) $(IMAGE) -hostname=$(shell hostname)
$(UPTODATE): $(EXE) Dockerfile
docker build -t $(IMAGE) .
touch $@
$(EXE): main.go
docker run --rm -v "$$PWD":/usr/src/$(EXE) -w /usr/src/$(EXE) golang:1.6 go build -v
clean:
- rm -rf $(UPTODATE) $(EXE)
- docker rmi $(IMAGE)
+138
View File
@@ -0,0 +1,138 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"net"
"net/http"
"os"
"os/exec"
"strconv"
"strings"
"time"
)
func main() {
hostname, _ := os.Hostname()
var (
addr = flag.String("addr", "/var/run/scope/plugins/iowait.sock", "unix socket to listen for connections on")
hostID = flag.String("hostname", hostname, "hostname of the host running this plugin")
)
flag.Parse()
log.Println("Starting...")
// Check we can get the iowait for the system
_, err := iowait()
if err != nil {
log.Fatal(err)
}
os.Remove(*addr)
listener, err := net.Listen("unix", *addr)
if err != nil {
log.Fatal(err)
}
defer func() {
listener.Close()
os.Remove(*addr)
}()
log.Printf("Listening on: unix://%s", *addr)
plugin := &Plugin{HostID: *hostID}
http.HandleFunc("/", plugin.Handshake)
http.HandleFunc("/report", plugin.Report)
if err := http.Serve(listener, nil); err != nil {
log.Printf("error: %v", err)
}
}
// Plugin groups the methods a plugin needs
type Plugin struct {
HostID string
}
// Handshake is the first method that scope calls on this plugin. It is used
// for the plugin to inform scope about the interfaces it fulfills, and to
// ensure both scope and the plugin support the same api version.
func (p *Plugin) Handshake(w http.ResponseWriter, r *http.Request) {
log.Printf("Probe %s handshake", r.FormValue("probe_id"))
err := json.NewEncoder(w).Encode(map[string]interface{}{
"name": "iowait",
"description": "Adds a graph of CPU IO Wait to hosts",
"interfaces": []string{"reporter"},
"api_version": "1",
})
if err != nil {
log.Printf("error: %v", err)
}
}
// Report is called by scope when a new report is needed. It is part of the
// "reporter" interface, which this plugin implements.
func (p *Plugin) Report(w http.ResponseWriter, r *http.Request) {
now := time.Now()
nowISO := now.Format(time.RFC3339)
value, err := iowait()
if err != nil {
log.Printf("error: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = json.NewEncoder(w).Encode(map[string]interface{}{
"Host": map[string]interface{}{
"nodes": map[string]interface{}{
p.HostID + ";<host>": map[string]interface{}{
"metrics": map[string]interface{}{
"iowait": map[string]interface{}{
"samples": []interface{}{
map[string]interface{}{
"date": nowISO,
"value": value,
},
},
},
},
},
},
"metric_templates": map[string]interface{}{
"iowait": map[string]interface{}{
"id": "iowait",
"label": "IO Wait",
"format": "percent",
"priority": 0.1, // low number so it shows up first
},
},
},
})
if err != nil {
log.Printf("error: %v", err)
}
}
// Get the latest iowait value
func iowait() (float64, error) {
out, err := exec.Command("iostat", "-c").Output()
if err != nil {
return 0, fmt.Errorf("iowait: %v", err)
}
// Linux 4.2.0-25-generic (a109563eab38) 04/01/16 _x86_64_(4 CPU)
//
// avg-cpu: %user %nice %system %iowait %steal %idle
// 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)
}
values := strings.Fields(lines[3])
if len(values) != 6 {
return 0, fmt.Errorf("iowait: unexpected output: %q", out)
}
return strconv.ParseFloat(values[3], 64)
}