diff --git a/handlers/badges.go b/handlers/badges.go index aaa95de..3bf5aab 100644 --- a/handlers/badges.go +++ b/handlers/badges.go @@ -54,7 +54,7 @@ const ( type Metric struct { Label string - Value float64 + Value int Time time.Time } @@ -121,7 +121,7 @@ func (handler *BadgesHandler) generateSVG(c *gin.Context, colorScheme ColorSchem prometheusOptions := badge.OptionStruct.(*models.PrometheusOptions) - metric, _ := queryMetric(handler.PrometheusAPIClient, prometheusOptions) + metric, _ := handler.queryMetric(badge, handler.PrometheusAPIClient, prometheusOptions) metricValue := int(metric.Value) label := metric.Label @@ -149,7 +149,7 @@ func (handler *BadgesHandler) generateSVG(c *gin.Context, colorScheme ColorSchem canvas.Rect(0, 0, int(labelContainerWidth), badgeHeight, string(colorScheme)) - //when there is metric label then use default label + //when there is a metric label then use default label if len(prometheusOptions.ValueField) == 0 { label = badge.Label } @@ -247,26 +247,66 @@ func (handler *BadgesHandler) CreateBadge(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"id": newID}) } -func queryMetric(client api.Client, options *models.PrometheusOptions) (*Metric, error) { - v1api := v1.NewAPI(client) - ctx, cancel := context.WithTimeout(context.Background(), metricRequestTimeout) - defer cancel() - - result, warnings, err := v1api.Query(ctx, options.Query, time.Now()) +func (handler *BadgesHandler) queryMetric(badge *models.Badge, client api.Client, options *models.PrometheusOptions) (*Metric, error) { + viewCount, err := handler.fetchViewCountFromDB(badge) if err != nil { - sentry.CaptureException(err) - return nil, fmt.Errorf("error querying Prometheus: %w", err) + return nil, err } - 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(options.ValueField)]), - Value: float64(v.Value), - Time: v.Timestamp.Time(), + + if viewCount.Count == 0 { + v1api := v1.NewAPI(client) + ctx, cancel := context.WithTimeout(context.Background(), metricRequestTimeout) + defer cancel() + + result, warnings, err := v1api.Query(ctx, options.Query, time.Now()) + if err != nil { + sentry.CaptureException(err) + return nil, fmt.Errorf("error querying Prometheus: %w", err) + } + if len(warnings) > 0 { + logrus.Warnf("Warnings: %v\n", warnings) + } + + for _, v := range result.(model.Vector) { + viewCount.Count = int(float64(v.Value)) } } - return &metric, nil + + if err = handler.updateMetric(viewCount); err != nil { + return nil, err + } + + return &Metric{ + Label: viewCount.BadgeULID, + Value: viewCount.Count, + Time: viewCount.UpdatedAt, + }, nil +} + +func (handler *BadgesHandler) fetchViewCountFromDB(badge *models.Badge) (*models.BadgeViewCount, error) { + var viewCount models.BadgeViewCount + err := handler.DB.Find(&viewCount, "ulid = ?", badge.ULID).Error + if err != nil { + if err != gorm.ErrRecordNotFound { + return nil, err + } + logrus.Infof("no db viewcount for %s", badge.ULID) + return &models.BadgeViewCount{ + BadgeULID: badge.ULID, + Count: 0, + }, nil + } + return &viewCount, nil +} + +func (handler *BadgesHandler) updateMetric(viewCount *models.BadgeViewCount) error { + viewCount.Count++ + err := handler.DB.Save(&viewCount).Error + if err != nil { + sentry.CaptureException(err) + logrus.Errorf("failed to update badge %s viewcount %v", viewCount.BadgeULID, err) + return err + } + handler.DB.Save(&models.ViewRecord{ULID: viewCount.BadgeULID}) + return nil } diff --git a/main.go b/main.go index 4ba6f19..6567603 100644 --- a/main.go +++ b/main.go @@ -55,7 +55,12 @@ func main() { defer db.Close() // Migrate the schema - db.AutoMigrate(&models.Badge{}, &models.Token{}, &models.User{}) + db.AutoMigrate( + &models.Badge{}, + &models.Token{}, + &models.User{}, + &models.ViewRecord{}, + &models.BadgeViewCount{}) client, err := api.NewClient(api.Config{ Address: "https://prometheus-api.home.toozhao.com", diff --git a/models/view_count.go b/models/view_count.go new file mode 100644 index 0000000..68b2ca0 --- /dev/null +++ b/models/view_count.go @@ -0,0 +1,9 @@ +package models + +import "github.com/jinzhu/gorm" + +type BadgeViewCount struct { + gorm.Model + BadgeULID string `gorm:"column:ulid;type:varchar;not null;unique_index" json:"ulid"` + Count int `json:"count" gorm:"not null;"` +} diff --git a/models/view_record.go b/models/view_record.go new file mode 100644 index 0000000..86ff116 --- /dev/null +++ b/models/view_record.go @@ -0,0 +1,10 @@ +package models + +import ( + "github.com/jinzhu/gorm" +) + +type ViewRecord struct { + gorm.Model + ULID string `gorm:"column:ulid;type:varchar;not null;" json:"ulid"` +} diff --git a/role.yaml b/role.yaml new file mode 100644 index 0000000..93b59ea --- /dev/null +++ b/role.yaml @@ -0,0 +1,28 @@ +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + namespace: apps + name: badger-ops +rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] + - apiGroups: ["monitoring.coreos.com"] + resources: ["servicemonitors"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] + +--- + +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: badger-role-binding + namespace: apps +subjects: + - kind: ServiceAccount + name: cd-robot + namespace: apps +roleRef: + kind: Role + name: badger-ops + apiGroup: rbac.authorization.k8s.io diff --git a/www/pages/index.vue b/www/pages/index.vue index 7401075..e28ddbb 100644 --- a/www/pages/index.vue +++ b/www/pages/index.vue @@ -161,14 +161,6 @@ > - - -

- Note: The page view count data may take up to 3 minutes to be - initialized. -

-
-