morffix/server/stats.go
Samuel Lorch 8e15cdd6e8
All checks were successful
/ release (push) Successful in 32s
Rework chart gen. Add Health, Tanscode, Task Status Charts
2024-07-13 23:18:47 +02:00

179 lines
4.3 KiB
Go

package server
import (
"bytes"
"context"
"fmt"
"html/template"
"log/slog"
"net/http"
"strings"
"git.lastassault.de/speatzle/morffix/constants"
"github.com/go-echarts/go-echarts/v2/charts"
"github.com/go-echarts/go-echarts/v2/opts"
"github.com/jackc/pgx/v5"
)
type StatsDisplay struct {
Charts []ChartData
}
type ChartData struct {
Element template.HTML
Script template.HTML
}
type PieValue struct {
Name string
Value int
}
type PieIntValue struct {
Id int
Value int
}
func generatePie(name string, data []opts.PieData) ChartData {
pie := charts.NewPie()
pie.SetGlobalOptions(
charts.WithInitializationOpts(opts.Initialization{
Theme: "dark",
BackgroundColor: "#111",
}),
charts.WithTitleOpts(opts.Title{
Title: name,
}))
pie.AddSeries(name, data).SetSeriesOptions(
charts.WithLabelOpts(
opts.Label{
Show: opts.Bool(true),
Formatter: "{b}: {c}",
}),
)
snippet := pie.RenderSnippet()
return ChartData{
Element: template.HTML(snippet.Element),
Script: template.HTML(snippet.Script),
}
}
func generateStats(ctx context.Context) ([]ChartData, error) {
data := []ChartData{}
rows, err := db.Query(ctx,
`SELECT COALESCE(jsonb_path_query_first(ffprobe_data, '$.streams[*] ? (@.codec_type == "video") ? (@.disposition.attached_pic == 0).codec_name')::text, 'Unknown') AS name, COUNT(*) AS value
FROM files
WHERE ffprobe_data IS NOT NULL
GROUP BY jsonb_path_query_first(ffprobe_data, '$.streams[*] ? (@.codec_type == "video") ? (@.disposition.attached_pic == 0).codec_name');`)
if err != nil {
return nil, fmt.Errorf("Query Codecs: %w", err)
}
codecCounts, err := pgx.CollectRows(rows, pgx.RowToStructByName[PieValue])
if err != nil {
return nil, fmt.Errorf("Collect Codec Data: %w", err)
}
res := []opts.PieData{}
for _, v := range codecCounts {
res = append(res, opts.PieData{
Name: strings.ReplaceAll(v.Name, "\"", ""),
Value: v.Value,
})
}
data = append(data, generatePie("Codecs", res))
rows, err = db.Query(ctx,
`SELECT health AS id, COUNT(*) AS value
FROM files
GROUP BY health;`)
if err != nil {
return nil, fmt.Errorf("Query Health: %w", err)
}
healthCounts, err := pgx.CollectRows(rows, pgx.RowToStructByName[PieIntValue])
if err != nil {
return nil, fmt.Errorf("Collect Health Data: %w", err)
}
res = []opts.PieData{}
for _, v := range healthCounts {
res = append(res, opts.PieData{
Name: constants.FileHealth(v.Id).String(),
Value: v.Value,
})
}
data = append(data, generatePie("Health", res))
rows, err = db.Query(ctx,
`SELECT transcode AS id, COUNT(*) AS value
FROM files
GROUP BY transcode;`)
if err != nil {
return nil, fmt.Errorf("Query Transcode: %w", err)
}
transcodeCounts, err := pgx.CollectRows(rows, pgx.RowToStructByName[PieIntValue])
if err != nil {
return nil, fmt.Errorf("Collect Transcode Data: %w", err)
}
res = []opts.PieData{}
for _, v := range transcodeCounts {
res = append(res, opts.PieData{
Name: constants.FileTranscode(v.Id).String(),
Value: v.Value,
})
}
data = append(data, generatePie("Transcode Status", res))
rows, err = db.Query(ctx,
`SELECT status AS id, COUNT(*) AS value
FROM tasks
GROUP BY status;`)
if err != nil {
return nil, fmt.Errorf("Query Task Status: %w", err)
}
taskStatusCounts, err := pgx.CollectRows(rows, pgx.RowToStructByName[PieIntValue])
if err != nil {
return nil, fmt.Errorf("Collect Task Status Data: %w", err)
}
res = []opts.PieData{}
for _, v := range taskStatusCounts {
res = append(res, opts.PieData{
Name: constants.TaskStatus(v.Id).String(),
Value: v.Value,
})
}
data = append(data, generatePie("Task Status", res))
return data, nil
}
func handleStats(w http.ResponseWriter, r *http.Request) {
data, err := generateStats(r.Context())
if err != nil {
slog.ErrorContext(r.Context(), "Generate Stats:", "err", err)
http.Error(w, "Generate Stats: "+err.Error(), http.StatusInternalServerError)
return
}
buf := bytes.Buffer{}
err = templates.ExecuteTemplate(&buf, constants.STATS_TEMPLATE_NAME, StatsDisplay{
Charts: data,
})
if err != nil {
slog.ErrorContext(r.Context(), "Executing Stats Template", "err", err)
http.Error(w, "Error Executing Template: "+err.Error(), http.StatusInternalServerError)
return
}
_, err = w.Write(buf.Bytes())
if err != nil {
slog.ErrorContext(r.Context(), "Writing http Response", "err", err)
}
}