mirror of
https://github.com/weaveworks/scope.git
synced 2026-08-19 04:16:21 +00:00
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:
@@ -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)
|
||||
@@ -0,0 +1 @@
|
||||
iowait
|
||||
@@ -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"]
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user