mirror of
https://github.com/bloomberg/goldpinger.git
synced 2026-08-23 21:36:25 +00:00
Generated
+16
-1
@@ -362,6 +362,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"
|
||||
@@ -626,7 +639,9 @@
|
||||
"github.com/jessevdk/go-flags",
|
||||
"github.com/prometheus/client_golang/prometheus",
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp",
|
||||
"golang.org/x/net/context",
|
||||
"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",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name ?= goldpinger
|
||||
version ?= 1.3.0
|
||||
version ?= 1.4.0
|
||||
bin ?= goldpinger
|
||||
pkg ?= "github.com/bloomberg/goldpinger"
|
||||
tag = $(name):$(version)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
FROM scratch
|
||||
|
||||
ADD bin/goldpinger /goldpinger
|
||||
COPY static/index.html /static/index.html
|
||||
COPY ./static /static
|
||||
|
||||
ENTRYPOINT ["/goldpinger", "--static-file-path", "/static"]
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -110,6 +110,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 {
|
||||
|
||||
+72
-13
@@ -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);
|
||||
@@ -347,6 +394,18 @@ $("#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