Draw also stats of single instances in dashboard diagram (#462)

* allow multiple graphs

* draw single instances
This commit is contained in:
Marc Brugger 2024-12-15 12:06:09 +01:00 committed by GitHub
parent 00ee5415a5
commit 89aeec5f97
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 166 additions and 34 deletions

View File

@ -7,6 +7,8 @@ import (
"golang.org/x/exp/constraints"
)
const StatsTotal = "total"
var (
l = log.GetLogger("metrics")
@ -155,14 +157,11 @@ func initMetric(name string, metric *prometheus.GaugeVec) {
}
func Update(ims ...InstanceMetrics) {
s := OverallStats{}
for _, im := range ims {
update(im)
s[im.HostName] = im.Stats
stats[im.HostName] = im.Stats
}
stats = s
l.Debug("updated")
}
@ -241,12 +240,13 @@ type InstanceMetrics struct {
type OverallStats map[string]*model.Stats
func (s OverallStats) consolidate() *model.Stats {
total := model.NewStats()
for _, stats := range s {
total.Add(stats)
func (os OverallStats) consolidate() OverallStats {
consolidated := OverallStats{StatsTotal: model.NewStats()}
for host, stats := range os {
consolidated[host] = stats
consolidated[StatsTotal].Add(stats)
}
return total
return consolidated
}
func safeMetric[T Number](v *T) float64 {
@ -260,6 +260,10 @@ type Number interface {
constraints.Float | constraints.Integer
}
func GetStats() *model.Stats {
func GetStats() OverallStats {
return stats.consolidate()
}
func (os OverallStats) Total() *model.Stats {
return os[StatsTotal]
}

View File

@ -33,6 +33,8 @@ func (w *worker) handleSync(c *gin.Context) {
}
func (w *worker) handleRoot(c *gin.Context) {
total, dns, blocked, malware, adult := statsGraph()
c.HTML(http.StatusOK, "index.html", map[string]interface{}{
"DarkMode": w.cfg.API.DarkMode,
"Metrics": w.cfg.API.Metrics.Enabled,
@ -41,18 +43,18 @@ func (w *worker) handleRoot(c *gin.Context) {
"SyncStatus": w.status(),
"Stats": map[string]interface{}{
"Labels": getLast24Hours(),
"DNS": metrics.GetStats().DnsQueries,
"Blocked": metrics.GetStats().BlockedFiltering,
"BlockedPercentage": fmt.Sprintf("%.2f", (float64(*metrics.GetStats().NumBlockedFiltering)*100.0)/float64(*metrics.GetStats().NumDnsQueries)),
"Malware": metrics.GetStats().ReplacedSafebrowsing,
"MalwarePercentage": fmt.Sprintf("%.2f", (float64(*metrics.GetStats().NumReplacedSafebrowsing)*100.0)/float64(*metrics.GetStats().NumDnsQueries)),
"Adult": metrics.GetStats().ReplacedParental,
"AdultPercentage": fmt.Sprintf("%.2f", (float64(*metrics.GetStats().NumReplacedParental)*100.0)/float64(*metrics.GetStats().NumDnsQueries)),
"DNS": dns,
"Blocked": blocked,
"BlockedPercentage": fmt.Sprintf("%.2f", (float64(*total.NumBlockedFiltering)*100.0)/float64(*total.NumDnsQueries)),
"Malware": malware,
"MalwarePercentage": fmt.Sprintf("%.2f", (float64(*total.NumReplacedSafebrowsing)*100.0)/float64(*total.NumDnsQueries)),
"Adult": adult,
"AdultPercentage": fmt.Sprintf("%.2f", (float64(*total.NumReplacedParental)*100.0)/float64(*total.NumDnsQueries)),
"TotalDNS": metrics.GetStats().NumDnsQueries,
"TotalBlocked": metrics.GetStats().NumBlockedFiltering,
"TotalMalware": metrics.GetStats().NumReplacedSafebrowsing,
"TotalAdult": metrics.GetStats().NumReplacedParental,
"TotalDNS": total.NumDnsQueries,
"TotalBlocked": total.NumBlockedFiltering,
"TotalMalware": total.NumReplacedSafebrowsing,
"TotalAdult": total.NumReplacedParental,
},
},
)

View File

@ -181,20 +181,27 @@
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// Function to create minimal line charts
function createMinimalChart(canvasId, lineData, r, g, b) {
function createChart(canvasId, data) {
const ctx = document.getElementById(canvasId).getContext('2d');
const datasets = Array(data.length);
for (let i = 0; i < data.length; i++) {
datasets[i] = {
data: data[i].data,
title: data[i].title,
backgroundColor: `rgb(${data[i].r}, ${data[i].g}, ${data[i].b}, 0.2)`,
borderColor: `rgb(${data[i].r}, ${data[i].g}, ${data[i].b}, 1)`,
borderWidth: 3,
fill: data[i].fill,
pointRadius: 0,
}
}
new Chart(ctx, {
type: 'line',
data: {
labels: {{.Stats.Labels}},
datasets: [{
data: lineData,
backgroundColor: `rgb(${r}, ${g}, ${b}, 0.2)`,
borderColor: `rgb(${r}, ${g}, ${b}, 1)`,
borderWidth: 3,
fill: true,
pointRadius: 0,
}]
datasets: datasets
},
options: {
responsive: true,
@ -216,6 +223,9 @@
displayColors: false,
callbacks: {
label: function(tooltipItem) {
if (tooltipItem.dataset.title) {
return tooltipItem.raw + " - " + tooltipItem.dataset.title;
}
return tooltipItem.raw;
}
}
@ -237,10 +247,10 @@
});
}
createMinimalChart('dnsQueriesChart', {{.Stats.DNS}}, 78, 141, 245); // RGB Blue
createMinimalChart('blockedFiltersChart', {{.Stats.Blocked}}, 255, 94, 94); // RGB Red
createMinimalChart('malwareChart', {{.Stats.Malware}}, 110, 224, 122); // RGB Green
createMinimalChart('adultWebsitesChart', {{.Stats.Adult}}, 232, 198, 78); // RGB Yellow
createChart('dnsQueriesChart', {{.Stats.DNS}});
createChart('blockedFiltersChart', {{.Stats.Blocked}});
createChart('malwareChart', {{.Stats.Malware}});
createChart('adultWebsitesChart', {{.Stats.Adult}});
</script>
{{- end }}
</body>

116
pkg/sync/stats.go Normal file
View File

@ -0,0 +1,116 @@
package sync
import (
"strings"
"github.com/bakito/adguardhome-sync/pkg/client/model"
"github.com/bakito/adguardhome-sync/pkg/metrics"
"golang.org/x/exp/slices"
)
var (
blue = []int{78, 141, 245}
blueAlternatives = [][]int{
{44, 95, 163},
{122, 166, 247},
{30, 61, 92},
{93, 158, 255},
{58, 123, 213},
}
red = []int{255, 94, 94}
redAlternatives = [][]int{
{204, 59, 59},
{255, 127, 127},
{140, 36, 36},
{255, 153, 153},
{255, 66, 66},
}
yellow = []int{232, 198, 78}
yellowAlternatives = [][]int{
{196, 163, 60},
{255, 220, 110},
{140, 114, 36},
{250, 233, 156},
{212, 180, 84},
}
green = []int{110, 224, 122}
greenAlternatives = [][]int{
{68, 160, 80},
{142, 255, 158},
{44, 140, 63},
{163, 255, 192},
{85, 198, 102},
}
)
func statsGraph() (*model.Stats, []line, []line, []line, []line) {
s := metrics.GetStats()
t := s.Total()
dns := graphLines(t, s, blue, blueAlternatives, func(s *model.Stats) []int {
return *s.DnsQueries
})
blocked := graphLines(t, s, red, redAlternatives, func(s *model.Stats) []int {
return *s.BlockedFiltering
})
malware := graphLines(t, s, green, greenAlternatives, func(s *model.Stats) []int {
return *s.ReplacedSafebrowsing
})
adult := graphLines(t, s, yellow, yellowAlternatives, func(s *model.Stats) []int {
return *s.ReplacedParental
})
return t, dns, blocked, malware, adult
}
func graphLines(t *model.Stats, s metrics.OverallStats, baseColor []int, altColors [][]int, dataCB func(s *model.Stats) []int) []line {
g := &graph{
total: line{
Fill: true,
Title: "Total",
Data: dataCB(t),
R: baseColor[0],
G: baseColor[1],
B: baseColor[2],
},
}
var i int
for name, data := range s {
if name != metrics.StatsTotal {
g.replicas = append(g.replicas, line{
Fill: false,
Title: name,
Data: dataCB(data),
R: altColors[i%len(altColors)][0],
G: altColors[i%len(altColors)][1],
B: altColors[i%len(altColors)][2],
})
i++
}
}
lines := []line{g.total}
slices.SortFunc(g.replicas, func(a, b line) int {
return strings.Compare(a.Title, b.Title)
})
lines = append(lines, g.replicas...)
return lines
}
type graph struct {
total line
replicas []line
}
type line struct {
Data []int `json:"data"`
R int `json:"r"`
G int `json:"g"`
B int `json:"b"`
Title string `json:"title"`
Fill bool `json:"fill"`
}