2e112fe08c
continuous-integration/drone/push Build is passing
- Changed the default MinIO bucket name from "opplens-documents" to "opplens" across multiple configuration files. - Introduced caching for dashboard statistics in the service layer to improve performance and reduce redundant data fetching. - Implemented a mutex for thread-safe access to cached statistics, ensuring data integrity during concurrent requests. This update streamlines the configuration for the AI summarizer and optimizes the dashboard service, enhancing overall system efficiency.
163 lines
4.9 KiB
Go
163 lines
4.9 KiB
Go
package mongo
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"go.mongodb.org/mongo-driver/v2/bson"
|
|
mongodriver "go.mongodb.org/mongo-driver/v2/mongo"
|
|
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
|
)
|
|
|
|
const (
|
|
metricsCountersCollection = "metrics_counters"
|
|
AITranslationSuccessCounterKey = "ai_translation_successful_requests"
|
|
AITranslationSuccessCounterKeyDailyJob = "ai_translation_successful_requests_daily_job"
|
|
AITranslationSuccessCounterKeyManualTrigger = "ai_translation_successful_requests_manual_trigger"
|
|
)
|
|
|
|
// Counter provides atomic increment/read operations for named metrics stored in MongoDB.
|
|
type Counter struct {
|
|
mongo *ConnectionManager
|
|
}
|
|
|
|
// NewCounter creates a counter backed by the metrics_counters collection.
|
|
func NewCounter(mongo *ConnectionManager) *Counter {
|
|
return &Counter{mongo: mongo}
|
|
}
|
|
|
|
// Increment atomically increments the counter identified by key and returns the new value.
|
|
func (c *Counter) Increment(ctx context.Context, key string) (int64, error) {
|
|
if key == "" {
|
|
return 0, fmt.Errorf("mongo counter: key is required")
|
|
}
|
|
|
|
var res struct {
|
|
Count int64 `bson:"count"`
|
|
}
|
|
err := c.mongo.GetCollection(metricsCountersCollection).FindOneAndUpdate(
|
|
ctx,
|
|
bson.M{"_id": key},
|
|
bson.M{
|
|
"$inc": bson.M{"count": 1},
|
|
"$set": bson.M{"updated_at": time.Now().Unix()},
|
|
"$setOnInsert": bson.M{"_id": key},
|
|
},
|
|
options.FindOneAndUpdate().SetUpsert(true).SetReturnDocument(options.After),
|
|
).Decode(&res)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("mongo counter increment %q: %w", key, err)
|
|
}
|
|
|
|
return res.Count, nil
|
|
}
|
|
|
|
// Get returns the current value of the counter identified by key (0 when missing).
|
|
func (c *Counter) Get(ctx context.Context, key string) (int64, error) {
|
|
if key == "" {
|
|
return 0, fmt.Errorf("mongo counter: key is required")
|
|
}
|
|
|
|
var doc struct {
|
|
Count int64 `bson:"count"`
|
|
}
|
|
err := c.mongo.GetCollection(metricsCountersCollection).FindOne(ctx, bson.M{"_id": key}).Decode(&doc)
|
|
if err != nil {
|
|
if errors.Is(err, mongodriver.ErrNoDocuments) {
|
|
return 0, nil
|
|
}
|
|
return 0, fmt.Errorf("mongo counter get %q: %w", key, err)
|
|
}
|
|
|
|
return doc.Count, nil
|
|
}
|
|
|
|
// AITranslationSuccessCallback returns a callback that increments successful AI translation counters.
|
|
func AITranslationSuccessCallback(counter *Counter) func(ctx context.Context, source string) error {
|
|
return func(ctx context.Context, source string) error {
|
|
if _, err := counter.Increment(ctx, AITranslationSuccessCounterKey); err != nil {
|
|
return err
|
|
}
|
|
if _, err := counter.Increment(ctx, AITranslationSuccessDailyCounterKey(time.Now().UTC())); err != nil {
|
|
return err
|
|
}
|
|
if key := AITranslationSuccessCounterKeyForSource(source); key != "" {
|
|
_, err := counter.Increment(ctx, key)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// AITranslationSuccessDailyCounterKey returns the metrics key for successful translations on a given UTC day.
|
|
func AITranslationSuccessDailyCounterKey(day time.Time) string {
|
|
return AITranslationSuccessCounterKey + "_day:" + day.UTC().Format("2006-01-02")
|
|
}
|
|
|
|
// GetDailyCounts returns counter values for each UTC day from start through end inclusive.
|
|
func (c *Counter) GetDailyCounts(ctx context.Context, startDay, endDay time.Time) (map[string]int64, error) {
|
|
startDay = time.Date(startDay.Year(), startDay.Month(), startDay.Day(), 0, 0, 0, 0, time.UTC)
|
|
endDay = time.Date(endDay.Year(), endDay.Month(), endDay.Day(), 0, 0, 0, 0, time.UTC)
|
|
if endDay.Before(startDay) {
|
|
return map[string]int64{}, nil
|
|
}
|
|
|
|
keys := make([]string, 0)
|
|
dates := make([]string, 0)
|
|
for day := startDay; !day.After(endDay); day = day.AddDate(0, 0, 1) {
|
|
dates = append(dates, day.Format("2006-01-02"))
|
|
keys = append(keys, AITranslationSuccessDailyCounterKey(day))
|
|
}
|
|
|
|
counts := make(map[string]int64, len(dates))
|
|
for _, date := range dates {
|
|
counts[date] = 0
|
|
}
|
|
|
|
cursor, err := c.mongo.GetCollection(metricsCountersCollection).Find(ctx, bson.M{
|
|
"_id": bson.M{"$in": keys},
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("mongo counter get daily counts: %w", err)
|
|
}
|
|
defer cursor.Close(ctx)
|
|
|
|
keyToDate := make(map[string]string, len(keys))
|
|
for i, key := range keys {
|
|
keyToDate[key] = dates[i]
|
|
}
|
|
|
|
for cursor.Next(ctx) {
|
|
var doc struct {
|
|
ID string `bson:"_id"`
|
|
Count int64 `bson:"count"`
|
|
}
|
|
if err := cursor.Decode(&doc); err != nil {
|
|
return nil, fmt.Errorf("mongo counter decode daily count: %w", err)
|
|
}
|
|
date, ok := keyToDate[doc.ID]
|
|
if !ok {
|
|
continue
|
|
}
|
|
counts[date] = doc.Count
|
|
}
|
|
if err := cursor.Err(); err != nil {
|
|
return nil, fmt.Errorf("mongo counter iterate daily counts: %w", err)
|
|
}
|
|
|
|
return counts, nil
|
|
}
|
|
|
|
// AITranslationSuccessCounterKeyForSource maps a request source to its metrics counter key.
|
|
func AITranslationSuccessCounterKeyForSource(source string) string {
|
|
switch source {
|
|
case "daily_job":
|
|
return AITranslationSuccessCounterKeyDailyJob
|
|
case "manual_trigger":
|
|
return AITranslationSuccessCounterKeyManualTrigger
|
|
default:
|
|
return ""
|
|
}
|
|
} |