mirror of
https://github.com/bloomberg/goldpinger.git
synced 2026-02-20 04:49:55 +00:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
096b149afa | ||
|
|
5840353d9f | ||
|
|
ac9bf4224d | ||
|
|
7b2affea29 | ||
|
|
d1a86eae93 | ||
|
|
a2e5925210 | ||
|
|
d243f0fb59 | ||
|
|
477ba69a72 | ||
|
|
86064f208e | ||
|
|
d8f8c20927 | ||
|
|
7c43626b1e | ||
|
|
11ec058b3b | ||
|
|
c006eede86 | ||
|
|
a585b103f3 | ||
|
|
32823fd105 | ||
|
|
87792cffca | ||
|
|
369e9ece78 | ||
|
|
28af41e352 | ||
|
|
52ea5546aa | ||
|
|
9040b69933 | ||
|
|
3ce0c46f91 | ||
|
|
7b156add72 | ||
|
|
8b1dd49506 | ||
|
|
bd13d4d673 | ||
|
|
895af850a1 | ||
|
|
771f303062 | ||
|
|
b7c1d2dfb4 | ||
|
|
86ddcf9505 | ||
|
|
bf43dcff98 | ||
|
|
6de72deee0 | ||
|
|
6cd12ef2d5 | ||
|
|
8182369c02 | ||
|
|
82a0d6ae8c | ||
|
|
057e360c5b | ||
|
|
4cf5dea621 | ||
|
|
16a1d52741 | ||
|
|
0472791dae | ||
|
|
84a43a3d28 | ||
|
|
3f70a24804 | ||
|
|
593307dc01 | ||
|
|
950e2e4ab7 | ||
|
|
d8f0d696ea | ||
|
|
6deb5c3044 | ||
|
|
767d2dba7f | ||
|
|
2efee0f5e5 | ||
|
|
3a729bf196 | ||
|
|
fd84599157 | ||
|
|
f5c2763000 | ||
|
|
22b96d001d |
@@ -1,2 +1 @@
|
||||
.git/
|
||||
/vendor/
|
||||
|
||||
14
.travis.yml
14
.travis.yml
@@ -24,3 +24,17 @@ script:
|
||||
- make clean && make build-multistage
|
||||
- docker images
|
||||
- docker run `make version` --help
|
||||
|
||||
# build an image with the vendor folder
|
||||
- make clean && make vendor && make vendor-build
|
||||
- docker images
|
||||
- docker run `make version`-vendor --help
|
||||
|
||||
deploy:
|
||||
provider: script
|
||||
script: bash docker_deploy.sh
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
go: "1.10.x"
|
||||
condition: -n "$DOCKER_PASSWORD"
|
||||
|
||||
@@ -21,5 +21,4 @@ RUN make bin/goldpinger
|
||||
FROM scratch
|
||||
COPY --from=builder /go/src/github.com/bloomberg/goldpinger/bin/goldpinger /goldpinger
|
||||
COPY ./static /static
|
||||
ENTRYPOINT ["/goldpinger", "--static-file-path", "/static"]
|
||||
|
||||
ENTRYPOINT ["/goldpinger", "--static-file-path", "/static"]
|
||||
35
Gopkg.lock
generated
35
Gopkg.lock
generated
@@ -33,6 +33,14 @@
|
||||
pruneopts = "UT"
|
||||
revision = "3a771d992973f24aa725d07868b467d1ddfceafb"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:998cf998358a303ac2430c386ba3fd3398477d6013153d3c6e11432765cc9ae6"
|
||||
name = "github.com/cespare/xxhash"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "3b82fb7d186719faeedd0c2864f868c74fbf79a1"
|
||||
version = "v2.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6f82cacd0af5921e99bf3f46748705239b36489464f4529a1589bc895764fb18"
|
||||
name = "github.com/docker/go-units"
|
||||
@@ -355,6 +363,14 @@
|
||||
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2731ee5852c26a3585f70d1a6cff8422383b98bad405eb6611cd4a2334be1df6"
|
||||
name = "github.com/stuartnelson3/go-rendezvous"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "16dc0292e5f420d89a7dc4319775a1e5e461dc01"
|
||||
version = "v0.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3f3a05ae0b95893d90b9b3b5afdb79a9b3d96e4e36e099d841ae602e4aca0da8"
|
||||
name = "golang.org/x/crypto"
|
||||
@@ -362,6 +378,19 @@
|
||||
pruneopts = "UT"
|
||||
revision = "de0752318171da717af4ce24d0a2e8626afaeb11"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:09c175055d303dadf3f82513c66e6da0b95ba22bc8e5d267a2674d16d95ea77a"
|
||||
name = "golang.org/x/image"
|
||||
packages = [
|
||||
"font",
|
||||
"font/basicfont",
|
||||
"font/plan9font",
|
||||
"math/fixed",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "31aff87c08e9a5e5d524279a564f96968336f886"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:eb8583d4582ffbc5c6d3ec00132cd0835101edde42b6f24eaafa628d1596f881"
|
||||
@@ -612,6 +641,7 @@
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/cespare/xxhash",
|
||||
"github.com/go-openapi/errors",
|
||||
"github.com/go-openapi/loads",
|
||||
"github.com/go-openapi/runtime",
|
||||
@@ -626,7 +656,10 @@
|
||||
"github.com/jessevdk/go-flags",
|
||||
"github.com/prometheus/client_golang/prometheus",
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp",
|
||||
"golang.org/x/net/context",
|
||||
"github.com/stuartnelson3/go-rendezvous",
|
||||
"golang.org/x/image/font",
|
||||
"golang.org/x/image/font/basicfont",
|
||||
"golang.org/x/image/math/fixed",
|
||||
"golang.org/x/net/netutil",
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"k8s.io/client-go/kubernetes",
|
||||
|
||||
@@ -73,6 +73,10 @@
|
||||
name = "k8s.io/client-go"
|
||||
version = "9.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/stuartnelson3/go-rendezvous"
|
||||
version = "0.2.0"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
||||
19
Makefile
19
Makefile
@@ -1,10 +1,10 @@
|
||||
name ?= goldpinger
|
||||
version ?= 1.3.0
|
||||
version ?= 1.5.1
|
||||
bin ?= goldpinger
|
||||
pkg ?= "github.com/bloomberg/goldpinger"
|
||||
tag = $(name):$(version)
|
||||
goos ?= ${GOOS}
|
||||
namespace ?= ""
|
||||
namespace ?= "bloomberg/"
|
||||
files = $(shell find . -iname "*.go")
|
||||
|
||||
|
||||
@@ -38,8 +38,19 @@ push:
|
||||
|
||||
run:
|
||||
go run ./cmd/goldpinger/main.go
|
||||
|
||||
|
||||
version:
|
||||
@echo $(tag)
|
||||
|
||||
.PHONY: clean vendor swagger build build-multistage tag push run version
|
||||
|
||||
vendor-build:
|
||||
docker build -t $(tag)-vendor --build-arg TAG=$(tag) -f ./build/Dockerfile-vendor .
|
||||
|
||||
vendor-tag:
|
||||
docker tag $(tag)-vendor $(namespace)$(tag)-vendor
|
||||
|
||||
vendor-push:
|
||||
docker push $(namespace)$(tag)-vendor
|
||||
|
||||
|
||||
.PHONY: clean vendor swagger build build-multistage vendor-build vendor-tag vendor-push tag push run version
|
||||
|
||||
42
README.md
42
README.md
@@ -38,11 +38,20 @@ If you'd like to know more, you can watch [our presentation at Kubecon 2018 Seat
|
||||
|
||||
## Quick start
|
||||
|
||||
Getting from sources:
|
||||
|
||||
```sh
|
||||
go get github.com/bloomberg/goldpinger/cmd/goldpinger
|
||||
goldpinger --help
|
||||
```
|
||||
|
||||
Getting from [docker hub](https://hub.docker.com/r/bloomberg/goldpinger):
|
||||
|
||||
```sh
|
||||
# get from docker hub
|
||||
docker pull bloomberg/goldpinger
|
||||
```
|
||||
|
||||
Note, that in order to guarantee correct versions of dependencies, the project [uses `dep`](./Makefile).
|
||||
|
||||
|
||||
@@ -73,7 +82,7 @@ Building from source code consists of compiling the binary and building a [Docke
|
||||
|
||||
```sh
|
||||
# step 0: check out the code into your $GOPATH
|
||||
go get github.com/bloomberg/goldpinger/cmd/goldpinger
|
||||
go get github.com/bloomberg/goldpinger/cmd/goldpinger
|
||||
cd $GOPATH/src/github.com/bloomberg/goldpinger
|
||||
|
||||
# step 1: download the dependencies via dep ensure
|
||||
@@ -103,9 +112,7 @@ namespace="docker.io/myhandle/" make push
|
||||
### Example YAML
|
||||
|
||||
|
||||
Here's an example of what you can do (using the in-cluster authentication to `Kubernetes` apiserver).
|
||||
|
||||
:warning: Replace `docker.io/mynamespace-replaceme/goldpinger:1.0.0` with the actual tag you built.
|
||||
Here's an example of what you can do (using the in-cluster authentication to `Kubernetes` apiserver).
|
||||
|
||||
```yaml
|
||||
---
|
||||
@@ -119,12 +126,12 @@ spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: goldpinger
|
||||
version: "1.0.0"
|
||||
version: "1.5.0"
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: goldpinger
|
||||
version: "1.0.0"
|
||||
version: "1.5.0"
|
||||
spec:
|
||||
containers:
|
||||
- name: goldpinger
|
||||
@@ -138,10 +145,27 @@ spec:
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: spec.nodeName
|
||||
image: "docker.io/mynamespace-replaceme/goldpinger:1.0.0"
|
||||
# podIP is used to select a randomized subset of nodes to ping.
|
||||
- name: POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
image: "docker.io/bloomberg/goldpinger:1.5.0"
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: http
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 80
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 5
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 80
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 5
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
@@ -191,7 +215,7 @@ You can click on various nodes to gray out the clutter and see more information.
|
||||
|
||||
### API
|
||||
|
||||
The API exposed is via a well-defined [`Swagger` spec](./swagger.yml).
|
||||
The API exposed is via a well-defined [`Swagger` spec](./swagger.yml).
|
||||
|
||||
The spec is used to generate both the server and the client of `Goldpinger`. If you make changes, you can re-generate them using [go-swagger](https://github.com/go-swagger/go-swagger) via [`make swagger`](./Makefile)
|
||||
|
||||
@@ -247,3 +271,5 @@ Before you create that PR, please make sure you read [CONTRIBUTING](./CONTRIBUTI
|
||||
## License
|
||||
|
||||
Please read the [LICENSE](./LICENSE) file here.
|
||||
|
||||
For each version built by travis, there is also an additional version, appended with `-vendor`, which contains all source code of the dependencies used in `goldpinger`.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
FROM scratch
|
||||
|
||||
ADD bin/goldpinger /goldpinger
|
||||
COPY static/index.html /static/index.html
|
||||
COPY bin/goldpinger /goldpinger
|
||||
COPY static /static
|
||||
|
||||
ENTRYPOINT ["/goldpinger", "--static-file-path", "/static"]
|
||||
|
||||
3
build/Dockerfile-vendor
Normal file
3
build/Dockerfile-vendor
Normal file
@@ -0,0 +1,3 @@
|
||||
ARG TAG
|
||||
FROM $TAG
|
||||
COPY vendor /goldpinger-vendor-sources
|
||||
@@ -96,6 +96,14 @@ func main() {
|
||||
goldpinger.GoldpingerConfig.Port = server.Port
|
||||
}
|
||||
|
||||
if goldpinger.GoldpingerConfig.PodIP == "" {
|
||||
log.Println("PodIP not set: pinging all pods")
|
||||
}
|
||||
if goldpinger.GoldpingerConfig.PingNumber == 0 {
|
||||
log.Println("--ping-number set to 0: pinging all pods")
|
||||
}
|
||||
goldpinger.GoldpingerConfig.PodSelecter = goldpinger.NewPodSelecter(goldpinger.GoldpingerConfig.PingNumber, goldpinger.GoldpingerConfig.PodIP, goldpinger.GetAllPods)
|
||||
|
||||
server.ConfigureAPI()
|
||||
goldpinger.StartUpdater()
|
||||
|
||||
|
||||
8
docker_deploy.sh
Executable file
8
docker_deploy.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
|
||||
docker login -u "$DOCKER_USER" -p "$DOCKER_PASSWORD" \
|
||||
&& make tag \
|
||||
&& make push \
|
||||
&& make vendor-tag \
|
||||
&& make vendor-push
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
@@ -38,7 +37,12 @@ spec:
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: spec.nodeName
|
||||
image: "docker.io/mynamespace-replaceme/goldpinger:1.1.0"
|
||||
# podIP is used to select a randomized subset of nodes to ping.
|
||||
- name: POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
image: "docker.io/bloomberg/goldpinger:1.4.0"
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: http
|
||||
|
||||
@@ -5,19 +5,19 @@ metadata:
|
||||
name: goldpinger
|
||||
labels:
|
||||
app: goldpinger
|
||||
version: "1.1.0"
|
||||
version: "1.5.0"
|
||||
spec:
|
||||
updateStrategy:
|
||||
type: RollingUpdate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: goldpinger
|
||||
version: "1.1.0"
|
||||
version: "1.5.0"
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: goldpinger
|
||||
version: "1.1.0"
|
||||
version: "1.5.0"
|
||||
spec:
|
||||
# if you'd like to use a secret to inject a kubeconfig, you can do it like this
|
||||
volumes:
|
||||
@@ -43,10 +43,27 @@ spec:
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: spec.nodeName
|
||||
image: "docker.io/mynamespace-replaceme/goldpinger:1.1.0"
|
||||
# podIP is used to select randomized subset of nodes to ping.
|
||||
- name: POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
image: "docker.io/bloomberg/goldpinger:1.5.0"
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: http
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 80
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 5
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 80
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 5
|
||||
volumeMounts:
|
||||
- mountPath: /.kube/
|
||||
name: kubeconfig
|
||||
|
||||
@@ -27,14 +27,14 @@ import (
|
||||
|
||||
// CheckNeighbours queries the kubernetes API server for all other goldpinger pods
|
||||
// then calls Ping() on each one
|
||||
func CheckNeighbours() models.CheckResults {
|
||||
return PingAllPods(GetAllPods())
|
||||
func CheckNeighbours(ps *PodSelecter) models.CheckResults {
|
||||
return PingAllPods(ps.SelectPods())
|
||||
}
|
||||
|
||||
// CheckNeighboursNeighbours queries the kubernetes API server for all other goldpinger
|
||||
// pods then calls Check() on each one
|
||||
func CheckNeighboursNeighbours() *models.CheckAllResults {
|
||||
return CheckAllPods(GetAllPods())
|
||||
func CheckNeighboursNeighbours(ps *PodSelecter) *models.CheckAllResults {
|
||||
return CheckAllPods(ps.SelectPods())
|
||||
}
|
||||
|
||||
type PingAllPodsResult struct {
|
||||
|
||||
@@ -24,8 +24,11 @@ var GoldpingerConfig = struct {
|
||||
KubeConfigPath string `long:"kubeconfig" description:"Path to kubeconfig file" env:"KUBECONFIG"`
|
||||
RefreshInterval int `long:"refresh-interval" description:"If > 0, will create a thread and collect stats every n seconds" env:"REFRESH_INTERVAL" default:"30"`
|
||||
Hostname string `long:"hostname" description:"Hostname to use" env:"HOSTNAME"`
|
||||
PodIP string `long:"pod-ip" description:"Pod IP to use" env:"POD_IP"`
|
||||
PingNumber uint `long:"ping-number" description:"Number of peers to ping. A value of 0 indicates all peers should be pinged." default:"0" env:"PING_NUMBER"`
|
||||
Port int `long:"client-port-override" description:"(for testing) use this port when calling other instances" env:"CLIENT_PORT_OVERRIDE"`
|
||||
UseHostIP bool `long:"use-host-ip" description:"When making the calls, use host ip (defaults to pod ip)" env:"USE_HOST_IP"`
|
||||
LabelSelector string `long:"label-selector" description:"label selector to use to discover goldpinger pods in the cluster" env:"LABEL_SELECTOR" default:"app=goldpinger"`
|
||||
KubernetesClient *kubernetes.Clientset
|
||||
*PodSelecter
|
||||
}{}
|
||||
|
||||
146
pkg/goldpinger/heatmap.go
Normal file
146
pkg/goldpinger/heatmap.go
Normal file
@@ -0,0 +1,146 @@
|
||||
// Copyright 2018 Bloomberg Finance L.P.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This file is safe to edit. Once it exists it will not be overwritten
|
||||
|
||||
package goldpinger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"log"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/basicfont"
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
func addLabel(img *image.RGBA, x, y int, text string) {
|
||||
drawer := &font.Drawer{
|
||||
Dst: img,
|
||||
Src: image.NewUniform(color.RGBA{25, 200, 25, 255}),
|
||||
Face: basicfont.Face7x13,
|
||||
Dot: fixed.Point26_6{fixed.Int26_6(x * 64), fixed.Int26_6(y * 64)},
|
||||
}
|
||||
drawer.DrawString(text)
|
||||
}
|
||||
|
||||
// Calculates the color of the box to draw based on the latency and tresholds
|
||||
// We are aiming at slightly more palatable colors than just moving from 255 green to 255 red,
|
||||
// so we will use 25B, and then move from (25R, 200G) to (200R, 25G), so our scale is effectively 350 points
|
||||
func getPingBoxColor(latency int64, tresholdLatencies [3]int64) *color.RGBA {
|
||||
var red, green uint8 = 25, 200
|
||||
if latency > tresholdLatencies[2] {
|
||||
red, green = 200, 25
|
||||
} else if latency >= tresholdLatencies[1] {
|
||||
red, green = 200, 200
|
||||
diff := (float32(latency-tresholdLatencies[1]) / float32(tresholdLatencies[2]-tresholdLatencies[1])) * 175
|
||||
green = green - uint8(diff)
|
||||
} else if latency >= tresholdLatencies[0] {
|
||||
red, green = 25, 200
|
||||
diff := (float32(latency-tresholdLatencies[0]) / float32(tresholdLatencies[1]-tresholdLatencies[0])) * 175
|
||||
red = red + uint8(diff)
|
||||
}
|
||||
return &color.RGBA{red, green, 25, 255}
|
||||
}
|
||||
|
||||
func drawPingBox(img *image.RGBA, _x, _y, size int, color *color.RGBA) {
|
||||
for x := _x; x < _x+size; x++ {
|
||||
for y := _y; y < _y+size; y++ {
|
||||
img.Set(x, y, *color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getPingBoxCoordinates(col, row, boxSize, padding int) (int, int) {
|
||||
return col * (boxSize + padding), row * (boxSize + padding)
|
||||
}
|
||||
|
||||
// HeatmapHandler returns a PNG with a heatmap representation
|
||||
func HeatmapHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// parse the query to set the parameters
|
||||
query := r.URL.Query()
|
||||
|
||||
// get the results
|
||||
checkResults := CheckAllPods(GetAllPods())
|
||||
|
||||
// set some sizes
|
||||
numberOfPods := len(checkResults.Responses)
|
||||
legendSize := 200
|
||||
boxSize := 14
|
||||
paddingSize := 1
|
||||
heatmapSize := numberOfPods*(boxSize+paddingSize) + boxSize*2
|
||||
tresholdLatencies := [3]int64{1, 10, 100}
|
||||
for index := range tresholdLatencies {
|
||||
stringValue := query["t"+fmt.Sprintf("%d", index)]
|
||||
if len(stringValue) == 0 {
|
||||
continue
|
||||
}
|
||||
if v, err := strconv.ParseInt(stringValue[0], 0, 64); err == nil && v >= 0 {
|
||||
tresholdLatencies[index] = v
|
||||
}
|
||||
}
|
||||
|
||||
canvas := image.NewRGBA(image.Rect(0, 0, heatmapSize+legendSize, heatmapSize))
|
||||
|
||||
// establish an order and fix the max delay
|
||||
var keys []string
|
||||
for sourceIP := range checkResults.Responses {
|
||||
keys = append(keys, sourceIP)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
order := make(map[string]int)
|
||||
for index, key := range keys {
|
||||
order[key] = index
|
||||
}
|
||||
|
||||
// draw all the boxes
|
||||
for sourceIP, results := range checkResults.Responses {
|
||||
if *results.OK {
|
||||
for destinationIP, response := range results.Response {
|
||||
x, y := getPingBoxCoordinates(order[sourceIP], order[destinationIP], boxSize, paddingSize)
|
||||
color := getPingBoxColor(response.ResponseTimeMs, tresholdLatencies)
|
||||
drawPingBox(canvas, boxSize+x, boxSize+y, boxSize, color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// draw the legend
|
||||
for index, ip := range keys {
|
||||
// ip
|
||||
addLabel(canvas, heatmapSize, (index+1)*(boxSize+paddingSize)+13, fmt.Sprintf("%d", index)+": "+ip)
|
||||
// rows
|
||||
addLabel(canvas, 0, (index+1)*(boxSize+paddingSize)+13, fmt.Sprintf("%d", index))
|
||||
// columns
|
||||
addLabel(canvas, (index+1)*(boxSize+paddingSize), 13, fmt.Sprintf("%d", index))
|
||||
}
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
if err := png.Encode(buffer, canvas); err != nil {
|
||||
log.Println("error encoding png", err)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(buffer.Bytes())))
|
||||
if _, err := w.Write(buffer.Bytes()); err != nil {
|
||||
log.Println("error writing heatmap buffer out", err)
|
||||
}
|
||||
}
|
||||
61
pkg/goldpinger/pod_selecter.go
Normal file
61
pkg/goldpinger/pod_selecter.go
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2018 Bloomberg Finance L.P.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package goldpinger
|
||||
|
||||
import (
|
||||
"github.com/cespare/xxhash"
|
||||
rendezvous "github.com/stuartnelson3/go-rendezvous"
|
||||
)
|
||||
|
||||
// PodSelecter selects the result of getPods() down to count instances
|
||||
// according to a rendezvous hash.
|
||||
type PodSelecter struct {
|
||||
count uint
|
||||
podIP string
|
||||
getPods func() map[string]string
|
||||
}
|
||||
|
||||
// NewPodSelecter creates a new PodSelecter struct.
|
||||
func NewPodSelecter(count uint, podIP string, getPods func() map[string]string) *PodSelecter {
|
||||
if podIP == "" {
|
||||
// If podIP is blank, then we can't use the rendezvous hash to
|
||||
// assign the IP correctly. Setting count=0 will force all pods
|
||||
// to be pinged.
|
||||
count = 0
|
||||
}
|
||||
return &PodSelecter{
|
||||
count: count,
|
||||
podIP: podIP,
|
||||
getPods: getPods,
|
||||
}
|
||||
}
|
||||
|
||||
// SelectPods returns a map of pods filtered according to its configuration.
|
||||
func (p *PodSelecter) SelectPods() map[string]string {
|
||||
allPods := p.getPods()
|
||||
if p.count == 0 || p.count >= uint(len(allPods)) {
|
||||
return allPods
|
||||
}
|
||||
rzv := rendezvous.New([]string{}, rendezvous.Hasher(xxhash.Sum64String))
|
||||
for podIP := range allPods {
|
||||
rzv.Add(podIP)
|
||||
}
|
||||
matches := rzv.LookupN(p.podIP, p.count)
|
||||
toPing := make(map[string]string)
|
||||
for _, podIP := range matches {
|
||||
toPing[podIP] = allPods[podIP]
|
||||
}
|
||||
return toPing
|
||||
}
|
||||
@@ -21,15 +21,15 @@ import (
|
||||
)
|
||||
|
||||
func StartUpdater() {
|
||||
|
||||
if GoldpingerConfig.RefreshInterval <= 0 {
|
||||
log.Println("Not creating updater, period is 0")
|
||||
return
|
||||
}
|
||||
|
||||
// start the updater
|
||||
go func() {
|
||||
for {
|
||||
results := PingAllPods(GetAllPods())
|
||||
results := PingAllPods(GoldpingerConfig.PodSelecter.SelectPods())
|
||||
var troublemakers []string
|
||||
for podIP, value := range results {
|
||||
if *value.OK != true {
|
||||
|
||||
@@ -47,6 +47,7 @@ func configureFlags(api *operations.GoldpingerAPI) {
|
||||
|
||||
func configureAPI(api *operations.GoldpingerAPI) http.Handler {
|
||||
// configure the api here
|
||||
ps := goldpinger.GoldpingerConfig.PodSelecter
|
||||
api.ServeError = errors.ServeError
|
||||
|
||||
api.JSONConsumer = runtime.JSONConsumer()
|
||||
@@ -61,13 +62,13 @@ func configureAPI(api *operations.GoldpingerAPI) http.Handler {
|
||||
api.CheckServicePodsHandler = operations.CheckServicePodsHandlerFunc(
|
||||
func(params operations.CheckServicePodsParams) middleware.Responder {
|
||||
goldpinger.CountCall("received", "check")
|
||||
return operations.NewCheckServicePodsOK().WithPayload(goldpinger.CheckNeighbours())
|
||||
return operations.NewCheckServicePodsOK().WithPayload(goldpinger.CheckNeighbours(ps))
|
||||
})
|
||||
|
||||
api.CheckAllPodsHandler = operations.CheckAllPodsHandlerFunc(
|
||||
func(params operations.CheckAllPodsParams) middleware.Responder {
|
||||
goldpinger.CountCall("received", "check_all")
|
||||
return operations.NewCheckAllPodsOK().WithPayload(goldpinger.CheckNeighboursNeighbours())
|
||||
return operations.NewCheckAllPodsOK().WithPayload(goldpinger.CheckNeighboursNeighbours(ps))
|
||||
})
|
||||
|
||||
api.HealthzHandler = operations.HealthzHandlerFunc(
|
||||
@@ -110,6 +111,8 @@ func fileServerMiddleware(next http.Handler) http.Handler {
|
||||
fileServer := http.FileServer(http.Dir(goldpinger.GoldpingerConfig.StaticFilePath))
|
||||
if r.URL.Path == "/" {
|
||||
http.StripPrefix("/", fileServer).ServeHTTP(w, r)
|
||||
} else if r.URL.Path == "/heatmap.png" {
|
||||
goldpinger.HeatmapHandler(w, r)
|
||||
} else if strings.HasPrefix(r.URL.Path, "/static/") {
|
||||
http.StripPrefix("/static/", fileServer).ServeHTTP(w, r)
|
||||
} else {
|
||||
|
||||
@@ -49,7 +49,7 @@ limitations under the License.
|
||||
<script type="text/javascript" src="static/sigma.js/1.1.0/plugins/sigma.renderers.parallelEdges.min.js"></script>
|
||||
<script type="text/javascript" src="static/sigma.js/1.1.0/plugins/sigma.renderers.snapshot.min.js"></script>
|
||||
<script type="text/javascript" src="static/sigma.js/1.1.0/plugins/sigma.statistics.HITS.min.js"></script>
|
||||
|
||||
|
||||
<!-- Bootstrap -->
|
||||
<link rel="stylesheet" href="static/bootstrap/3.3.7/css/bootstrap.min.css" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="static/bootstrap/3.3.7/css/bootstrap-theme.min.css" crossorigin="anonymous">
|
||||
@@ -86,6 +86,7 @@ limitations under the License.
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="active"><a href="#">Graph</a></li>
|
||||
<li><a href="#" id="show-data">Data</a></li>
|
||||
<li><a href="#" id="show-heatmap">Heatmap</a></li>
|
||||
<li><a href="check_all" >Raw</a></li>
|
||||
<li><a href="metrics" >Metrics</a></li>
|
||||
</ul>
|
||||
@@ -106,24 +107,70 @@ limitations under the License.
|
||||
|
||||
</div>
|
||||
|
||||
<div id="modal-window-code" class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="modal-title">title</h4>
|
||||
</div>
|
||||
<div class="modal-body" id="modal-body">
|
||||
body
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
<div id="modal-window-code" class="modal fade bs-example-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="modal-title">title</h4>
|
||||
</div>
|
||||
<div class="modal-body" id="modal-body">
|
||||
body
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="modal-window-heatmap" class="modal fade bs-example-modal-lg" tabindex="-2" role="dialog" aria-labelledby="myLargeModalLabel">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="modal-title">Heatmap</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<div id="heatmap-body" style="padding: 10px"></div>
|
||||
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon" style="width: 200px">Good treshold</span>
|
||||
<input id="t0" type="number" step="1" class="form-control" placeholder="2" value="2">
|
||||
<span class="input-group-addon">milliseconds</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon" style="width: 200px">Warning treshold</span>
|
||||
<input id="t1" type="number" step="1" class="form-control" placeholder="5" value="5">
|
||||
<span class="input-group-addon">milliseconds</span>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon" style="width: 200px">Problem treshold</span>
|
||||
<input id="t2" type="number" step="1" class="form-control" placeholder="100" value="100">
|
||||
<span class="input-group-addon">milliseconds</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-success" id="update-heatmap" type="button">refresh</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
var getHeatmapUrl = function(){
|
||||
return "heatmap.png?"
|
||||
+ "t0=" + Number($("#t0").val())
|
||||
+ "&t1=" + Number($("#t1").val())
|
||||
+ "&t2=" + Number($("#t2").val())
|
||||
+ "&now=" + Date.now();
|
||||
}
|
||||
|
||||
var fetchJSON = function(url) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
console.log("calling " + url);
|
||||
@@ -172,7 +219,7 @@ var main = function(timeout){
|
||||
|
||||
loadingDialog(true);
|
||||
|
||||
fetchJSON("/check_all?timeout="+timeout)
|
||||
fetchJSON("check_all?timeout="+timeout)
|
||||
.then(function(data){
|
||||
|
||||
loadingDialog(false);
|
||||
@@ -182,6 +229,7 @@ var main = function(timeout){
|
||||
|
||||
// prepare nodes
|
||||
var nodes = [];
|
||||
var podIPs = [];
|
||||
var resp = data.responses;
|
||||
for (let podIP in resp) {
|
||||
let value = resp[podIP];
|
||||
@@ -192,8 +240,8 @@ var main = function(timeout){
|
||||
"y": 0,
|
||||
_data: value,
|
||||
});
|
||||
podIPs.push(podIP);
|
||||
};
|
||||
//console.log(nodes);
|
||||
|
||||
// prepare the edges
|
||||
var edges = [];
|
||||
@@ -210,7 +258,26 @@ var main = function(timeout){
|
||||
};
|
||||
}
|
||||
};
|
||||
//console.log(edges);
|
||||
console.log(edges);
|
||||
console.log(nodes);
|
||||
|
||||
// if running goldpinger with PING_NUMBER, then we need to add nodes for the edges
|
||||
// that are not reporting but are reported to
|
||||
edges.forEach(function(edge) {
|
||||
[edge.source, edge.target].forEach(function(value) {
|
||||
if (podIPs.indexOf(value) == -1){
|
||||
nodes.push({
|
||||
"label": value,
|
||||
"id": value,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
_data: {},
|
||||
});
|
||||
podIPs.push(value);
|
||||
}
|
||||
})
|
||||
})
|
||||
console.log(nodes);
|
||||
|
||||
|
||||
// build the actual graph
|
||||
@@ -305,7 +372,7 @@ var main = function(timeout){
|
||||
$('#modal-title').html(node.id);
|
||||
$('#modal-body').html(prepareJSON(node._data));
|
||||
|
||||
// show the things
|
||||
// show the things
|
||||
$('#modal-window-code').modal('show');
|
||||
});
|
||||
s.bind("clickStage", function (e) {
|
||||
@@ -321,7 +388,7 @@ var main = function(timeout){
|
||||
$('#modal-title').html(edge.id);
|
||||
$('#modal-body').html(prepareJSON(edge._data));
|
||||
|
||||
// show the things
|
||||
// show the things
|
||||
$('#modal-window-code').modal('show');
|
||||
});
|
||||
})
|
||||
@@ -337,16 +404,28 @@ main();
|
||||
|
||||
$("#show-data").click(function (e) {
|
||||
// fill the voids
|
||||
$('#modal-title').html("/check_all");
|
||||
$('#modal-title').html("check_all");
|
||||
$('#modal-body').html(prepareJSON(global_data));
|
||||
|
||||
// show the things
|
||||
// show the things
|
||||
$('#modal-window-code').modal('show');
|
||||
});
|
||||
$("#reload-graph").click(function (e) {
|
||||
s.kill();
|
||||
main();
|
||||
});
|
||||
$("#show-heatmap").click(function (e) {
|
||||
updateHeatmap();
|
||||
$('#modal-window-heatmap').modal('show');
|
||||
});
|
||||
var updateHeatmap = function(){
|
||||
$('#heatmap-body').html(
|
||||
'<img src="' + getHeatmapUrl() + '" />'
|
||||
);
|
||||
}
|
||||
$("#update-heatmap").click(function (e) {
|
||||
updateHeatmap();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user