mirror of
https://github.com/wahyd4/badger.git
synced 2026-08-09 05:15:58 +10:00
202 lines
6.6 KiB
Go
202 lines
6.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"time"
|
|
|
|
svg "github.com/ajstarks/svgo"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/prometheus/client_golang/api"
|
|
v1 "github.com/prometheus/client_golang/api/prometheus/v1"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
"github.com/prometheus/common/model"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
const (
|
|
radius = 0
|
|
fontSize = 12
|
|
textMarginX = 6.0
|
|
textMarginY = 14
|
|
badgeHeight = 20
|
|
textStyle = "text-anchor:start;font-size:12px;fill:white;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;"
|
|
badgeExpireSeconds = 60
|
|
labelWidthPortion = 0.65
|
|
ColorSchemeGreen = "fill-opacity:1.00; fill:rgb(49, 197, 83);"
|
|
ColorSchemeOrange = "fill-opacity:1.00; fill:rgb(255, 87, 34);"
|
|
ColorSchemeBlue = "fill-opacity:1.00; fill:rgb(32, 99, 155);"
|
|
)
|
|
|
|
type ColorScheme string
|
|
|
|
var (
|
|
ErrNoSuchBadgeFound = errors.New("no such badge can be found, please make sure you have provided the correct information")
|
|
queriesMap = map[string]queryParam{
|
|
"mel": {Color: ColorSchemeGreen, Query: "air_index_iaqi_pm25_v{city_name=\"Melbourne CBD\"}", ValueField: "city_name"},
|
|
"chengdu": {Color: ColorSchemeGreen, Query: "air_index_iaqi_pm25_v{city_name=\"Chengdu (成都)\"}", ValueField: "city_name"},
|
|
"dockerhub": {Color: ColorSchemeGreen, Query: "docker_hub_pull_count{name=\"aria2-ui\"}", ValueField: "name"},
|
|
"github": {Color: ColorSchemeGreen, Query: "github_repository_stars{name=\"aria2-ariang-docker\"}", ValueField: "name"},
|
|
"badge-requests-count": {Color: ColorSchemeBlue, Query: "ceil(sum(increase(badge_requested_total[500d])))", Label: "Badge Req Count"},
|
|
"junv-github-profile": {Color: ColorSchemeBlue, Query: "ceil(sum(increase(badge_requested_total{badge=\"junv-github-profile\"}[500d])))", Label: "Page Views"},
|
|
"aria2-ariang-docker": {Color: ColorSchemeBlue, Query: "ceil(sum(increase(badge_requested_total{badge=\"aria2-ariang-docker\"}[500d])))", Label: "Page Views"},
|
|
"aria2-ariang-x-docker-compose": {Color: ColorSchemeBlue, Query: "ceil(sum(increase(badge_requested_total{badge=\"aria2-ariang-x-docker-compose\"}[500d])))", Label: "Page Views"},
|
|
"work-in-australia": {Color: ColorSchemeBlue, Query: "ceil(sum(increase(badge_requested_total{badge=\"work-in-australia\"}[500d])))", Label: "Page Views"},
|
|
"aria-ui-dockerhub": {Color: ColorSchemeBlue, Query: "ceil(sum(increase(badge_requested_total{badge=\"aria-ui-dockerhub\"}[500d])))", Label: "Page Views"},
|
|
}
|
|
|
|
badgeRequested = promauto.NewCounterVec(prometheus.CounterOpts{
|
|
Name: "badge_requested_total",
|
|
Help: "The total request number of badges",
|
|
}, []string{"badge"})
|
|
|
|
badgeGenerationTime = promauto.NewHistogram(prometheus.HistogramOpts{
|
|
Name: "badge_generation_time",
|
|
Help: "The time consumed for badge generation",
|
|
})
|
|
)
|
|
|
|
type Metric struct {
|
|
Label string
|
|
Value float64
|
|
Time time.Time
|
|
}
|
|
|
|
type queryParam struct {
|
|
Color ColorScheme
|
|
Query string
|
|
ValueField string // nullable
|
|
Label string // when ValueField is null, then the program will use this field as the label
|
|
}
|
|
|
|
func main() {
|
|
client, err := api.NewClient(api.Config{
|
|
Address: "https://prometheus-api.home.toozhao.com",
|
|
})
|
|
if err != nil {
|
|
fmt.Printf("Error creating client: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
r := gin.Default()
|
|
|
|
r.GET("/", func(c *gin.Context) {
|
|
c.JSON(200, gin.H{"msg": "Wanna create some badges for your website or Github repo from various data sources? It's coming soon."})
|
|
})
|
|
|
|
r.GET("/ping", func(c *gin.Context) {
|
|
c.JSON(200, gin.H{"msg": "Hello!"})
|
|
})
|
|
|
|
r.GET("/svg/:name", func(c *gin.Context) {
|
|
if err := generateSVG(client, c); err != nil {
|
|
logrus.Error(err.Error())
|
|
if err == ErrNoSuchBadgeFound {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "please try it again"})
|
|
}
|
|
})
|
|
|
|
r.StaticFile("/favicon.ico", "static/favicon.ico")
|
|
r.StaticFile("/robots.txt", "static/robots.txt")
|
|
|
|
go func() {
|
|
http.Handle("/metrics", promhttp.Handler())
|
|
if err := http.ListenAndServe(":9090", nil); err != nil {
|
|
logrus.Error(err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
}()
|
|
|
|
log.Fatal(r.Run())
|
|
}
|
|
|
|
func generateSVG(client api.Client, c *gin.Context) error {
|
|
startTime := time.Now()
|
|
|
|
queryName := c.Param("name")
|
|
query, ok := queriesMap[queryName]
|
|
if !ok {
|
|
return ErrNoSuchBadgeFound
|
|
}
|
|
metric := queryMetric(client, query)
|
|
|
|
metricValue := int(metric.Value)
|
|
label := metric.Label
|
|
|
|
valueRectWidth := 40
|
|
labelContainerWidth := 120
|
|
|
|
if len(label) <= 10 {
|
|
labelContainerWidth = 74
|
|
}
|
|
|
|
if metricValue > 10000 && metricValue < 1000000 {
|
|
valueRectWidth = 50
|
|
} else if metricValue >= 1000000 {
|
|
valueRectWidth = 66
|
|
}
|
|
badgeWidth := labelContainerWidth + valueRectWidth
|
|
|
|
w := c.Writer
|
|
w.Header().Set("Content-Type", "image/svg+xml")
|
|
w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d, s-maxage=%d", badgeExpireSeconds, badgeExpireSeconds))
|
|
canvas := svg.New(w)
|
|
canvas.Start(badgeWidth, badgeHeight)
|
|
canvas.Rect(0, 0, badgeWidth, badgeHeight, "fill-opacity:1.00; fill:rgb(90,90,90); padding: 2px 5px;")
|
|
|
|
//labelContainerWidth := len(metric.Label)*fontSize + textMarginX
|
|
|
|
canvas.Rect(0, 0, int(labelContainerWidth), badgeHeight, string(query.Color))
|
|
|
|
//when there is metric label then use default label
|
|
if len(query.ValueField) == 0 {
|
|
label = query.Label
|
|
}
|
|
canvas.Text(textMarginX, textMarginY, label, textStyle)
|
|
|
|
valueText := strconv.Itoa(metricValue)
|
|
canvas.Text(labelContainerWidth+textMarginX, textMarginY, valueText, textStyle)
|
|
canvas.End()
|
|
|
|
badgeGenerationTime.Observe(float64(time.Since(startTime).Microseconds()))
|
|
badgeRequested.With(map[string]string{"badge": queryName}).Inc()
|
|
|
|
return nil
|
|
}
|
|
|
|
func queryMetric(client api.Client, query queryParam) *Metric {
|
|
v1api := v1.NewAPI(client)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
result, warnings, err := v1api.Query(ctx, query.Query, time.Now())
|
|
if err != nil {
|
|
fmt.Printf("Error querying Prometheus: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
if len(warnings) > 0 {
|
|
fmt.Printf("Warnings: %v\n", warnings)
|
|
}
|
|
var metric Metric
|
|
for _, v := range result.(model.Vector) {
|
|
metric = Metric{
|
|
Label: string(v.Metric[model.LabelName(query.ValueField)]),
|
|
Value: float64(v.Value),
|
|
Time: v.Timestamp.Time(),
|
|
}
|
|
}
|
|
return &metric
|
|
}
|