mirror of
https://github.com/chaitin/MonkeyCode.git
synced 2026-02-02 14:53:55 +08:00
feat: 数据上报
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/chaitin/MonkeyCode/backend/config"
|
||||
"github.com/chaitin/MonkeyCode/backend/db"
|
||||
"github.com/chaitin/MonkeyCode/backend/domain"
|
||||
billingv1 "github.com/chaitin/MonkeyCode/backend/internal/billing/handler/http/v1"
|
||||
dashv1 "github.com/chaitin/MonkeyCode/backend/internal/dashboard/handler/v1"
|
||||
v1 "github.com/chaitin/MonkeyCode/backend/internal/model/handler/http/v1"
|
||||
@@ -33,6 +34,7 @@ type Server struct {
|
||||
billingV1 *billingv1.BillingHandler
|
||||
version *version.VersionInfo
|
||||
report *report.Reporter
|
||||
reportuse domain.ReportUsecase
|
||||
}
|
||||
|
||||
func newServer() (*Server, error) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/GoYoko/web"
|
||||
"github.com/chaitin/MonkeyCode/backend/config"
|
||||
"github.com/chaitin/MonkeyCode/backend/db"
|
||||
"github.com/chaitin/MonkeyCode/backend/domain"
|
||||
v1_5 "github.com/chaitin/MonkeyCode/backend/internal/billing/handler/http/v1"
|
||||
repo7 "github.com/chaitin/MonkeyCode/backend/internal/billing/repo"
|
||||
usecase6 "github.com/chaitin/MonkeyCode/backend/internal/billing/usecase"
|
||||
@@ -28,6 +29,8 @@ import (
|
||||
"github.com/chaitin/MonkeyCode/backend/internal/proxy"
|
||||
"github.com/chaitin/MonkeyCode/backend/internal/proxy/repo"
|
||||
"github.com/chaitin/MonkeyCode/backend/internal/proxy/usecase"
|
||||
repo8 "github.com/chaitin/MonkeyCode/backend/internal/report/repo"
|
||||
usecase7 "github.com/chaitin/MonkeyCode/backend/internal/report/usecase"
|
||||
v1_3 "github.com/chaitin/MonkeyCode/backend/internal/user/handler/v1"
|
||||
repo5 "github.com/chaitin/MonkeyCode/backend/internal/user/repo"
|
||||
usecase4 "github.com/chaitin/MonkeyCode/backend/internal/user/usecase"
|
||||
@@ -86,6 +89,8 @@ func newServer() (*Server, error) {
|
||||
billingHandler := v1_5.NewBillingHandler(web, billingUsecase, authMiddleware, activeMiddleware)
|
||||
versionInfo := version.NewVersionInfo()
|
||||
reporter := report.NewReport(slogLogger, configConfig, versionInfo)
|
||||
reportRepo := repo8.NewReportRepo(client)
|
||||
reportUsecase := usecase7.NewReportUsecase(reportRepo, slogLogger, reporter)
|
||||
server := &Server{
|
||||
config: configConfig,
|
||||
web: web,
|
||||
@@ -98,6 +103,7 @@ func newServer() (*Server, error) {
|
||||
billingV1: billingHandler,
|
||||
version: versionInfo,
|
||||
report: reporter,
|
||||
reportuse: reportUsecase,
|
||||
}
|
||||
return server, nil
|
||||
}
|
||||
@@ -116,4 +122,5 @@ type Server struct {
|
||||
billingV1 *v1_5.BillingHandler
|
||||
version *version.VersionInfo
|
||||
report *report.Reporter
|
||||
reportuse domain.ReportUsecase
|
||||
}
|
||||
|
||||
41
backend/domain/report.go
Normal file
41
backend/domain/report.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/chaitin/MonkeyCode/backend/consts"
|
||||
)
|
||||
|
||||
type ReportUsecase interface {
|
||||
}
|
||||
|
||||
type ReportRepo interface {
|
||||
GetAdminCount(ctx context.Context) (int64, error)
|
||||
GetMemberCount(ctx context.Context) (int64, error)
|
||||
GetLast24HoursStats(ctx context.Context) (*ActivityStats, error)
|
||||
GetCurrentModels(ctx context.Context) ([]*ReportModelUsage, error)
|
||||
}
|
||||
|
||||
type ReportData struct {
|
||||
Timestamp string `json:"timestamp"` // 上报时间戳
|
||||
Version string `json:"version"` // 系统版本号
|
||||
MachineID string `json:"machine_id"` // 机器ID
|
||||
AdminCount int64 `json:"admin_count"` // 管理员数量
|
||||
MemberCount int64 `json:"member_count"` // 成员数量
|
||||
Last24Hours *ActivityStats `json:"last_24_hours"` // 最近24小时统计
|
||||
CurrentModels []*ReportModelUsage `json:"current_models"` // 当前使用的模型列表
|
||||
}
|
||||
|
||||
type ActivityStats struct {
|
||||
ChatCount int64 `json:"chat_count"` // 对话次数
|
||||
CompletionCount int64 `json:"completion_count"` // 补全生成次数
|
||||
AcceptedCount int64 `json:"accepted_count"` // 补全采纳次数
|
||||
TotalCodeLines int64 `json:"total_code_lines"` // 总代码量(行数)
|
||||
AcceptanceRate float64 `json:"acceptance_rate"` // 采纳率 (accepted_count / completion_count)
|
||||
}
|
||||
|
||||
type ReportModelUsage struct {
|
||||
ModelID string `json:"model_id"` // 模型ID
|
||||
ModelName string `json:"model_name"` // 模型名称
|
||||
ModelType consts.ModelType `json:"model_type"` // 模型类型 (llm/coder)
|
||||
}
|
||||
@@ -21,6 +21,8 @@ import (
|
||||
"github.com/chaitin/MonkeyCode/backend/internal/proxy"
|
||||
proxyrepo "github.com/chaitin/MonkeyCode/backend/internal/proxy/repo"
|
||||
proxyusecase "github.com/chaitin/MonkeyCode/backend/internal/proxy/usecase"
|
||||
reportrepo "github.com/chaitin/MonkeyCode/backend/internal/report/repo"
|
||||
reportuse "github.com/chaitin/MonkeyCode/backend/internal/report/usecase"
|
||||
userV1 "github.com/chaitin/MonkeyCode/backend/internal/user/handler/v1"
|
||||
userrepo "github.com/chaitin/MonkeyCode/backend/internal/user/repo"
|
||||
userusecase "github.com/chaitin/MonkeyCode/backend/internal/user/usecase"
|
||||
@@ -52,4 +54,6 @@ var Provider = wire.NewSet(
|
||||
erepo.NewExtensionRepo,
|
||||
eusecase.NewExtensionUsecase,
|
||||
version.NewVersionInfo,
|
||||
reportuse.NewReportUsecase,
|
||||
reportrepo.NewReportRepo,
|
||||
)
|
||||
|
||||
98
backend/internal/report/repo/report.go
Normal file
98
backend/internal/report/repo/report.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/chaitin/MonkeyCode/backend/consts"
|
||||
"github.com/chaitin/MonkeyCode/backend/db"
|
||||
"github.com/chaitin/MonkeyCode/backend/db/task"
|
||||
"github.com/chaitin/MonkeyCode/backend/db/user"
|
||||
"github.com/chaitin/MonkeyCode/backend/domain"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/cvt"
|
||||
)
|
||||
|
||||
type ReportRepo struct {
|
||||
db *db.Client
|
||||
}
|
||||
|
||||
func NewReportRepo(db *db.Client) domain.ReportRepo {
|
||||
r := &ReportRepo{db: db}
|
||||
return r
|
||||
}
|
||||
|
||||
// GetAdminCount implements domain.ReportRepo.
|
||||
func (r *ReportRepo) GetAdminCount(ctx context.Context) (int64, error) {
|
||||
count, err := r.db.Admin.Query().Count(ctx)
|
||||
return int64(count), err
|
||||
}
|
||||
|
||||
// GetCurrentModels implements domain.ReportRepo.
|
||||
func (r *ReportRepo) GetCurrentModels(ctx context.Context) ([]*domain.ReportModelUsage, error) {
|
||||
now := time.Now()
|
||||
models, err := r.db.Model.Query().
|
||||
WithTasks(func(tq *db.TaskQuery) {
|
||||
tq.Where(task.CreatedAtGTE(now.Add(-24 * time.Hour)))
|
||||
}).
|
||||
All(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cvt.Iter(models, func(_ int, m *db.Model) *domain.ReportModelUsage {
|
||||
return &domain.ReportModelUsage{
|
||||
ModelID: m.ID.String(),
|
||||
ModelName: m.ModelName,
|
||||
ModelType: m.ModelType,
|
||||
}
|
||||
}), nil
|
||||
}
|
||||
|
||||
// GetLast24HoursStats implements domain.ReportRepo.
|
||||
func (r *ReportRepo) GetLast24HoursStats(ctx context.Context) (*domain.ActivityStats, error) {
|
||||
now := time.Now()
|
||||
last24Hours := now.Add(-24 * time.Hour)
|
||||
|
||||
// 获取最近24小时的任务统计
|
||||
tasks, err := r.db.Task.Query().
|
||||
Where(task.CreatedAtGTE(last24Hours)).
|
||||
All(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stats := &domain.ActivityStats{}
|
||||
var completionCount, acceptedCount, totalCodeLines int64
|
||||
|
||||
for _, t := range tasks {
|
||||
switch t.ModelType {
|
||||
case consts.ModelTypeLLM:
|
||||
stats.ChatCount++
|
||||
case consts.ModelTypeCoder:
|
||||
completionCount++
|
||||
if t.IsAccept {
|
||||
acceptedCount++
|
||||
}
|
||||
}
|
||||
totalCodeLines += t.CodeLines
|
||||
}
|
||||
|
||||
stats.CompletionCount = completionCount
|
||||
stats.AcceptedCount = acceptedCount
|
||||
stats.TotalCodeLines = totalCodeLines
|
||||
|
||||
// 计算采纳率
|
||||
if completionCount > 0 {
|
||||
stats.AcceptanceRate = float64(acceptedCount) / float64(completionCount)
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// GetMemberCount implements domain.ReportRepo.
|
||||
func (r *ReportRepo) GetMemberCount(ctx context.Context) (int64, error) {
|
||||
count, err := r.db.User.Query().
|
||||
Where(user.Status(consts.UserStatusActive)).
|
||||
Count(ctx)
|
||||
return int64(count), err
|
||||
}
|
||||
71
backend/internal/report/usecase/report.go
Normal file
71
backend/internal/report/usecase/report.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"github.com/chaitin/MonkeyCode/backend/domain"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/report"
|
||||
"github.com/chaitin/MonkeyCode/backend/pkg/version"
|
||||
)
|
||||
|
||||
type ReportUsecase struct {
|
||||
repo domain.ReportRepo
|
||||
logger *slog.Logger
|
||||
reporter *report.Reporter
|
||||
}
|
||||
|
||||
func NewReportUsecase(repo domain.ReportRepo, logger *slog.Logger, reporter *report.Reporter) domain.ReportUsecase {
|
||||
r := &ReportUsecase{repo: repo, logger: logger, reporter: reporter}
|
||||
go r.Report()
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *ReportUsecase) Report() {
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
if err := r.innerReport(); err != nil {
|
||||
r.logger.With("error", err).Error("report failed")
|
||||
}
|
||||
|
||||
for range ticker.C {
|
||||
if err := r.innerReport(); err != nil {
|
||||
r.logger.With("error", err).Error("report failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ReportUsecase) innerReport() error {
|
||||
admins, err := r.repo.GetAdminCount(context.Background())
|
||||
if err != nil {
|
||||
return fmt.Errorf("get admin count failed: %w", err)
|
||||
}
|
||||
models, err := r.repo.GetCurrentModels(context.Background())
|
||||
if err != nil {
|
||||
return fmt.Errorf("get current models failed: %w", err)
|
||||
}
|
||||
stats, err := r.repo.GetLast24HoursStats(context.Background())
|
||||
if err != nil {
|
||||
return fmt.Errorf("get last 24 hours stats failed: %w", err)
|
||||
}
|
||||
members, err := r.repo.GetMemberCount(context.Background())
|
||||
if err != nil {
|
||||
return fmt.Errorf("get member count failed: %w", err)
|
||||
}
|
||||
|
||||
data := domain.ReportData{
|
||||
Timestamp: time.Now().Format(time.RFC3339),
|
||||
Version: version.Version,
|
||||
MachineID: r.reporter.GetMachineID(),
|
||||
AdminCount: admins,
|
||||
MemberCount: members,
|
||||
Last24Hours: stats,
|
||||
CurrentModels: models,
|
||||
}
|
||||
|
||||
if err := r.reporter.Report("monkeycode-metrics", data); err != nil {
|
||||
return fmt.Errorf("report failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -52,7 +52,7 @@ func (r *Reporter) readMachineID() (string, error) {
|
||||
return r.machineID, nil
|
||||
}
|
||||
|
||||
func (r *Reporter) report(data any) error {
|
||||
func (r *Reporter) Report(index string, data any) error {
|
||||
b, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -64,7 +64,7 @@ func (r *Reporter) report(data any) error {
|
||||
}
|
||||
|
||||
req := map[string]any{
|
||||
"index": "monkeycode-installation",
|
||||
"index": index,
|
||||
"id": uuid.NewString(),
|
||||
"data": encrypt,
|
||||
}
|
||||
@@ -77,6 +77,10 @@ func (r *Reporter) report(data any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reporter) GetMachineID() string {
|
||||
return r.machineID
|
||||
}
|
||||
|
||||
func (r *Reporter) ReportInstallation() error {
|
||||
if r.machineID != "" {
|
||||
return nil
|
||||
@@ -102,7 +106,7 @@ func (r *Reporter) ReportInstallation() error {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.report(InstallData{
|
||||
return r.Report("monkeycode-installation", InstallData{
|
||||
MachineID: id,
|
||||
Version: r.version.Version(),
|
||||
Timestamp: time.Now().Format(time.RFC3339),
|
||||
|
||||
Reference in New Issue
Block a user