Merge pull request #51 from seeker89/heatmap

Add heatmap
This commit is contained in:
Mikolaj Pawlikowski
2019-03-05 17:48:00 +00:00
committed by GitHub
6 changed files with 238 additions and 16 deletions
Generated
+16 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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"]
+146
View 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)
}
}
+2
View File
@@ -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
View File
@@ -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">&times;</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">&times;</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">&times;</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>