etcd/tools/rw-heatmaps/pkg/chart/heatmap_grid.go
Ivan Valdes 1576e2e5a4
tools/rw-heatmaps: reimplement in golang
Signed-off-by: Ivan Valdes <ivan@vald.es>
2024-03-09 19:26:29 -08:00

131 lines
3.6 KiB
Go

// Copyright 2024 The etcd Authors
//
// 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 chart
import (
"sort"
"go.etcd.io/etcd/tools/rw-heatmaps/v3/pkg/dataset"
)
// heatMapGrid holds X, Y, Z values for a heatmap.
type heatMapGrid struct {
x, y []float64
z [][]float64 // The Z values should be arranged in a 2D slice.
}
// newHeatMapGrid returns a new heatMapGrid.
func newHeatMapGrid(plotType string, records []dataset.DataRecord) *heatMapGrid {
x, y := populateGridAxes(records)
// Create a 2D slice to hold the Z values.
z := make([][]float64, len(y))
for i := range z {
z[i] = make([]float64, len(x))
for j := range z[i] {
recordIndex := i*len(x) + j
// If the recordIndex is out of range (incomplete data), break the loop.
if recordIndex >= len(records) {
break
}
record := records[recordIndex]
if plotType == "read" {
z[i][j] = record.AvgRead
} else {
z[i][j] = record.AvgWrite
}
}
}
return &heatMapGrid{x, y, z}
}
// newDeltaHeatMapGrid returns a new heatMapGrid for the delta heatmap.
func newDeltaHeatMapGrid(plotType string, records [][]dataset.DataRecord) *heatMapGrid {
delta := make([]dataset.DataRecord, len(records[0]))
for i := range records[0] {
delta[i] = dataset.DataRecord{
ConnSize: records[0][i].ConnSize,
ValueSize: records[0][i].ValueSize,
AvgRead: ((records[1][i].AvgRead - records[0][i].AvgRead) / records[0][i].AvgRead) * 100,
AvgWrite: ((records[1][i].AvgWrite - records[0][i].AvgWrite) / records[0][i].AvgWrite) * 100,
}
}
return newHeatMapGrid(plotType, delta)
}
// Dims returns the number of elements in the grid.
// It implements the plotter.GridXYZ interface.
func (h *heatMapGrid) Dims() (int, int) {
return len(h.x), len(h.y)
}
// Z returns the value of a grid cell at (c, r).
// It implements the plotter.GridXYZ interface.
func (h *heatMapGrid) Z(c, r int) float64 {
return h.z[r][c]
}
// X returns the coordinate for the column at index c.
// It implements the plotter.GridXYZ interface.
func (h *heatMapGrid) X(c int) float64 {
if c >= len(h.x) {
panic("index out of range")
}
return h.x[c]
}
// Y returns the coordinate for the row at index r.
// It implements the plotter.GridXYZ interface.
func (h *heatMapGrid) Y(r int) float64 {
if r >= len(h.y) {
panic("index out of range")
}
return h.y[r]
}
// populateGridAxes populates the X and Y axes for the heatmap grid.
func populateGridAxes(records []dataset.DataRecord) ([]float64, []float64) {
var xslice, yslice []float64
for _, record := range records {
xslice = append(xslice, float64(record.ConnSize))
yslice = append(yslice, float64(record.ValueSize))
}
// Sort and deduplicate the slices
xUnique := uniqueSortedFloats(xslice)
yUnique := uniqueSortedFloats(yslice)
return xUnique, yUnique
}
// uniqueSortedFloats returns a sorted slice of unique float64 values.
func uniqueSortedFloats(input []float64) []float64 {
unique := make([]float64, 0)
seen := make(map[float64]bool)
for _, value := range input {
if !seen[value] {
seen[value] = true
unique = append(unique, value)
}
}
sort.Float64s(unique)
return unique
}