summary.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847
  1. package model
  2. import (
  3. "cmp"
  4. "errors"
  5. "fmt"
  6. "net/http"
  7. "slices"
  8. "time"
  9. "github.com/shopspring/decimal"
  10. "golang.org/x/sync/errgroup"
  11. "gorm.io/gorm"
  12. "gorm.io/gorm/clause"
  13. )
  14. // only summary result only requests
  15. type Summary struct {
  16. ID int `gorm:"primaryKey"`
  17. Unique SummaryUnique `gorm:"embedded"`
  18. Data SummaryData `gorm:"embedded"`
  19. }
  20. type SummaryUnique struct {
  21. ChannelID int `gorm:"not null;uniqueIndex:idx_summary_unique,priority:1"`
  22. Model string `gorm:"size:64;not null;uniqueIndex:idx_summary_unique,priority:2"`
  23. HourTimestamp int64 `gorm:"not null;uniqueIndex:idx_summary_unique,priority:3,sort:desc"`
  24. }
  25. type Count struct {
  26. RequestCount int64 `json:"request_count"`
  27. RetryCount ZeroNullInt64 `json:"retry_count"`
  28. ExceptionCount ZeroNullInt64 `json:"exception_count"`
  29. Status4xxCount ZeroNullInt64 `json:"status_4xx_count"`
  30. Status5xxCount ZeroNullInt64 `json:"status_5xx_count"`
  31. Status400Count ZeroNullInt64 `json:"status_400_count"`
  32. Status429Count ZeroNullInt64 `json:"status_429_count"`
  33. Status500Count ZeroNullInt64 `json:"status_500_count"`
  34. }
  35. func (c *Count) AddRequest(status int, isRetry bool) {
  36. c.RequestCount++
  37. if status != http.StatusOK {
  38. c.ExceptionCount++
  39. }
  40. if isRetry {
  41. c.RetryCount++
  42. }
  43. if status >= 400 && status < 500 {
  44. c.Status4xxCount++
  45. }
  46. if status >= 500 && status < 600 {
  47. c.Status5xxCount++
  48. }
  49. if status == http.StatusBadRequest {
  50. c.Status400Count++
  51. }
  52. if status == http.StatusTooManyRequests {
  53. c.Status429Count++
  54. }
  55. if status == http.StatusInternalServerError {
  56. c.Status500Count++
  57. }
  58. }
  59. func (c *Count) Add(other Count) {
  60. c.RequestCount += other.RequestCount
  61. c.RetryCount += other.RetryCount
  62. c.ExceptionCount += other.ExceptionCount
  63. c.Status4xxCount += other.Status4xxCount
  64. c.Status5xxCount += other.Status5xxCount
  65. c.Status400Count += other.Status400Count
  66. c.Status429Count += other.Status429Count
  67. c.Status500Count += other.Status500Count
  68. }
  69. type SummaryData struct {
  70. Count
  71. Usage
  72. UsedAmount float64 `json:"used_amount"`
  73. TotalTimeMilliseconds int64 `json:"total_time_milliseconds,omitempty"`
  74. TotalTTFBMilliseconds int64 `json:"total_ttfb_milliseconds,omitempty"`
  75. }
  76. func (d *SummaryData) buildUpdateData(tableName string) map[string]any {
  77. data := map[string]any{}
  78. if d.UsedAmount > 0 {
  79. data["used_amount"] = gorm.Expr(tableName+".used_amount + ?", d.UsedAmount)
  80. }
  81. if d.RequestCount > 0 {
  82. data["request_count"] = gorm.Expr(tableName+".request_count + ?", d.RequestCount)
  83. }
  84. if d.RetryCount > 0 {
  85. data["retry_count"] = gorm.Expr(tableName+".retry_count + ?", d.RetryCount)
  86. }
  87. if d.ExceptionCount > 0 {
  88. data["exception_count"] = gorm.Expr(
  89. tableName+".exception_count + ?",
  90. d.ExceptionCount,
  91. )
  92. }
  93. if d.Status4xxCount > 0 {
  94. data["status4xx_count"] = gorm.Expr(
  95. tableName+".status4xx_count + ?",
  96. d.Status4xxCount,
  97. )
  98. }
  99. if d.Status5xxCount > 0 {
  100. data["status5xx_count"] = gorm.Expr(
  101. tableName+".status5xx_count + ?",
  102. d.Status5xxCount,
  103. )
  104. }
  105. if d.Status400Count > 0 {
  106. data["status400_count"] = gorm.Expr(
  107. tableName+".status400_count + ?",
  108. d.Status400Count,
  109. )
  110. }
  111. if d.Status429Count > 0 {
  112. data["status429_count"] = gorm.Expr(
  113. tableName+".status429_count + ?",
  114. d.Status429Count,
  115. )
  116. }
  117. if d.Status500Count > 0 {
  118. data["status500_count"] = gorm.Expr(
  119. tableName+".status500_count + ?",
  120. d.Status500Count,
  121. )
  122. }
  123. if d.TotalTimeMilliseconds > 0 {
  124. data["total_time_milliseconds"] = gorm.Expr(
  125. tableName+".total_time_milliseconds + ?",
  126. d.TotalTimeMilliseconds,
  127. )
  128. }
  129. if d.TotalTTFBMilliseconds > 0 {
  130. data["total_ttfb_milliseconds"] = gorm.Expr(
  131. tableName+".total_ttfb_milliseconds + ?",
  132. d.TotalTTFBMilliseconds,
  133. )
  134. }
  135. // usage update
  136. if d.InputTokens > 0 {
  137. data["input_tokens"] = gorm.Expr(
  138. fmt.Sprintf("COALESCE(%s.input_tokens, 0) + ?", tableName),
  139. d.InputTokens,
  140. )
  141. }
  142. if d.ImageInputTokens > 0 {
  143. data["image_input_tokens"] = gorm.Expr(
  144. fmt.Sprintf("COALESCE(%s.image_input_tokens, 0) + ?", tableName),
  145. d.ImageInputTokens,
  146. )
  147. }
  148. if d.AudioInputTokens > 0 {
  149. data["audio_input_tokens"] = gorm.Expr(
  150. fmt.Sprintf("COALESCE(%s.audio_input_tokens, 0) + ?", tableName),
  151. d.AudioInputTokens,
  152. )
  153. }
  154. if d.OutputTokens > 0 {
  155. data["output_tokens"] = gorm.Expr(
  156. fmt.Sprintf("COALESCE(%s.output_tokens, 0) + ?", tableName),
  157. d.OutputTokens,
  158. )
  159. }
  160. if d.TotalTokens > 0 {
  161. data["total_tokens"] = gorm.Expr(
  162. fmt.Sprintf("COALESCE(%s.total_tokens, 0) + ?", tableName),
  163. d.TotalTokens,
  164. )
  165. }
  166. if d.CachedTokens > 0 {
  167. data["cached_tokens"] = gorm.Expr(
  168. fmt.Sprintf("COALESCE(%s.cached_tokens, 0) + ?", tableName),
  169. d.CachedTokens,
  170. )
  171. }
  172. if d.CacheCreationTokens > 0 {
  173. data["cache_creation_tokens"] = gorm.Expr(
  174. fmt.Sprintf("COALESCE(%s.cache_creation_tokens, 0) + ?", tableName),
  175. d.CacheCreationTokens,
  176. )
  177. }
  178. if d.WebSearchCount > 0 {
  179. data["web_search_count"] = gorm.Expr(
  180. fmt.Sprintf("COALESCE(%s.web_search_count, 0) + ?", tableName),
  181. d.WebSearchCount,
  182. )
  183. }
  184. return data
  185. }
  186. func (l *Summary) BeforeCreate(_ *gorm.DB) (err error) {
  187. if l.Unique.ChannelID == 0 {
  188. return errors.New("channel id is required")
  189. }
  190. if l.Unique.Model == "" {
  191. return errors.New("model is required")
  192. }
  193. if l.Unique.HourTimestamp == 0 {
  194. return errors.New("hour timestamp is required")
  195. }
  196. if err := validateHourTimestamp(l.Unique.HourTimestamp); err != nil {
  197. return err
  198. }
  199. return err
  200. }
  201. var hourTimestampDivisor = int64(time.Hour.Seconds())
  202. func validateHourTimestamp(hourTimestamp int64) error {
  203. if hourTimestamp%hourTimestampDivisor != 0 {
  204. return errors.New("hour timestamp must be an exact hour")
  205. }
  206. return nil
  207. }
  208. func CreateSummaryIndexs(db *gorm.DB) error {
  209. indexes := []string{
  210. "CREATE INDEX IF NOT EXISTS idx_summary_channel_hour ON summaries (channel_id, hour_timestamp DESC)",
  211. "CREATE INDEX IF NOT EXISTS idx_summary_model_hour ON summaries (model, hour_timestamp DESC)",
  212. }
  213. for _, index := range indexes {
  214. if err := db.Exec(index).Error; err != nil {
  215. return err
  216. }
  217. }
  218. return nil
  219. }
  220. func UpsertSummary(unique SummaryUnique, data SummaryData) error {
  221. err := validateHourTimestamp(unique.HourTimestamp)
  222. if err != nil {
  223. return err
  224. }
  225. for range 3 {
  226. result := LogDB.
  227. Model(&Summary{}).
  228. Where(
  229. "channel_id = ? AND model = ? AND hour_timestamp = ?",
  230. unique.ChannelID,
  231. unique.Model,
  232. unique.HourTimestamp,
  233. ).
  234. Updates(data.buildUpdateData("summaries"))
  235. err = result.Error
  236. if err != nil {
  237. return err
  238. }
  239. if result.RowsAffected > 0 {
  240. return nil
  241. }
  242. err = createSummary(unique, data)
  243. if err == nil {
  244. return nil
  245. }
  246. if !errors.Is(err, gorm.ErrDuplicatedKey) {
  247. return err
  248. }
  249. }
  250. return err
  251. }
  252. func createSummary(unique SummaryUnique, data SummaryData) error {
  253. return LogDB.
  254. Clauses(clause.OnConflict{
  255. Columns: []clause.Column{
  256. {Name: "channel_id"},
  257. {Name: "model"},
  258. {Name: "hour_timestamp"},
  259. },
  260. DoUpdates: clause.Assignments(data.buildUpdateData("summaries")),
  261. }).
  262. Create(&Summary{
  263. Unique: unique,
  264. Data: data,
  265. }).Error
  266. }
  267. func getChartData(
  268. start, end time.Time,
  269. channelID int,
  270. modelName string,
  271. timeSpan TimeSpanType,
  272. timezone *time.Location,
  273. ) ([]ChartData, error) {
  274. query := LogDB.Model(&Summary{})
  275. if channelID != 0 {
  276. query = query.Where("channel_id = ?", channelID)
  277. }
  278. if modelName != "" {
  279. query = query.Where("model = ?", modelName)
  280. }
  281. switch {
  282. case !start.IsZero() && !end.IsZero():
  283. query = query.Where("hour_timestamp BETWEEN ? AND ?", start.Unix(), end.Unix())
  284. case !start.IsZero():
  285. query = query.Where("hour_timestamp >= ?", start.Unix())
  286. case !end.IsZero():
  287. query = query.Where("hour_timestamp <= ?", end.Unix())
  288. }
  289. const selectFields = "hour_timestamp as timestamp, sum(used_amount) as used_amount, " +
  290. "sum(request_count) as request_count, sum(retry_count) as retry_count, sum(exception_count) as exception_count, sum(status4xx_count) as status4xx_count, sum(status5xx_count) as status5xx_count, sum(status400_count) as status400_count, sum(status429_count) as status429_count, sum(status500_count) as status500_count, " +
  291. "sum(total_time_milliseconds) as total_time_milliseconds, sum(total_ttfb_milliseconds) as total_ttfb_milliseconds, " +
  292. "sum(input_tokens) as input_tokens, sum(image_input_tokens) as image_input_tokens, sum(audio_input_tokens) as audio_input_tokens, sum(output_tokens) as output_tokens, " +
  293. "sum(cached_tokens) as cached_tokens, sum(cache_creation_tokens) as cache_creation_tokens, " +
  294. "sum(total_tokens) as total_tokens, sum(web_search_count) as web_search_count"
  295. query = query.
  296. Select(selectFields).
  297. Group("timestamp")
  298. var chartData []ChartData
  299. err := query.Find(&chartData).Error
  300. if err != nil {
  301. return nil, err
  302. }
  303. if len(chartData) > 0 && timeSpan != TimeSpanHour {
  304. chartData = aggregateDataToSpan(chartData, timeSpan, timezone)
  305. }
  306. slices.SortFunc(chartData, func(a, b ChartData) int {
  307. return cmp.Compare(a.Timestamp, b.Timestamp)
  308. })
  309. return chartData, nil
  310. }
  311. func getGroupChartData(
  312. group string,
  313. start, end time.Time,
  314. tokenName, modelName string,
  315. timeSpan TimeSpanType,
  316. timezone *time.Location,
  317. ) ([]ChartData, error) {
  318. query := LogDB.Model(&GroupSummary{})
  319. if group != "" {
  320. query = query.Where("group_id = ?", group)
  321. }
  322. if tokenName != "" {
  323. query = query.Where("token_name = ?", tokenName)
  324. }
  325. if modelName != "" {
  326. query = query.Where("model = ?", modelName)
  327. }
  328. switch {
  329. case !start.IsZero() && !end.IsZero():
  330. query = query.Where("hour_timestamp BETWEEN ? AND ?", start.Unix(), end.Unix())
  331. case !start.IsZero():
  332. query = query.Where("hour_timestamp >= ?", start.Unix())
  333. case !end.IsZero():
  334. query = query.Where("hour_timestamp <= ?", end.Unix())
  335. }
  336. const selectFields = "hour_timestamp as timestamp, sum(used_amount) as used_amount, " +
  337. "sum(request_count) as request_count, sum(exception_count) as exception_count, sum(status4xx_count) as status4xx_count, sum(status5xx_count) as status5xx_count, sum(status400_count) as status400_count, sum(status429_count) as status429_count, sum(status500_count) as status500_count, " +
  338. "sum(total_time_milliseconds) as total_time_milliseconds, sum(total_ttfb_milliseconds) as total_ttfb_milliseconds, " +
  339. "sum(input_tokens) as input_tokens, sum(image_input_tokens) as image_input_tokens, sum(audio_input_tokens) as audio_input_tokens, sum(output_tokens) as output_tokens, " +
  340. "sum(cached_tokens) as cached_tokens, sum(cache_creation_tokens) as cache_creation_tokens, " +
  341. "sum(total_tokens) as total_tokens, sum(web_search_count) as web_search_count"
  342. query = query.
  343. Select(selectFields).
  344. Group("timestamp")
  345. var chartData []ChartData
  346. err := query.Find(&chartData).Error
  347. if err != nil {
  348. return nil, err
  349. }
  350. if len(chartData) > 0 && timeSpan != TimeSpanHour {
  351. chartData = aggregateDataToSpan(chartData, timeSpan, timezone)
  352. }
  353. slices.SortFunc(chartData, func(a, b ChartData) int {
  354. return cmp.Compare(a.Timestamp, b.Timestamp)
  355. })
  356. return chartData, nil
  357. }
  358. func GetUsedChannels(start, end time.Time) ([]int, error) {
  359. return getLogGroupByValues[int]("channel_id", start, end)
  360. }
  361. func GetUsedModels(start, end time.Time) ([]string, error) {
  362. return getLogGroupByValues[string]("model", start, end)
  363. }
  364. func GetGroupUsedModels(group, tokenName string, start, end time.Time) ([]string, error) {
  365. return getGroupLogGroupByValues[string]("model", group, tokenName, start, end)
  366. }
  367. func GetGroupUsedTokenNames(group string, start, end time.Time) ([]string, error) {
  368. return getGroupLogGroupByValues[string]("token_name", group, "", start, end)
  369. }
  370. func getLogGroupByValues[T cmp.Ordered](
  371. field string,
  372. start, end time.Time,
  373. ) ([]T, error) {
  374. type Result struct {
  375. Value T
  376. UsedAmount float64
  377. RequestCount int64
  378. }
  379. var results []Result
  380. var query *gorm.DB
  381. query = LogDB.
  382. Model(&Summary{})
  383. switch {
  384. case !start.IsZero() && !end.IsZero():
  385. query = query.Where("hour_timestamp BETWEEN ? AND ?", start.Unix(), end.Unix())
  386. case !start.IsZero():
  387. query = query.Where("hour_timestamp >= ?", start.Unix())
  388. case !end.IsZero():
  389. query = query.Where("hour_timestamp <= ?", end.Unix())
  390. }
  391. err := query.
  392. Select(
  393. field + " as value, SUM(request_count) as request_count, SUM(used_amount) as used_amount",
  394. ).
  395. Group(field).
  396. Find(&results).Error
  397. if err != nil {
  398. return nil, err
  399. }
  400. slices.SortFunc(results, func(a, b Result) int {
  401. if a.UsedAmount != b.UsedAmount {
  402. return cmp.Compare(b.UsedAmount, a.UsedAmount)
  403. }
  404. if a.RequestCount != b.RequestCount {
  405. return cmp.Compare(b.RequestCount, a.RequestCount)
  406. }
  407. return cmp.Compare(a.Value, b.Value)
  408. })
  409. values := make([]T, len(results))
  410. for i, result := range results {
  411. values[i] = result.Value
  412. }
  413. return values, nil
  414. }
  415. func getGroupLogGroupByValues[T cmp.Ordered](
  416. field, group, tokenName string,
  417. start, end time.Time,
  418. ) ([]T, error) {
  419. type Result struct {
  420. Value T
  421. UsedAmount float64
  422. RequestCount int64
  423. }
  424. var results []Result
  425. query := LogDB.
  426. Model(&GroupSummary{})
  427. if group != "" {
  428. query = query.Where("group_id = ?", group)
  429. }
  430. if tokenName != "" {
  431. query = query.Where("token_name = ?", tokenName)
  432. }
  433. switch {
  434. case !start.IsZero() && !end.IsZero():
  435. query = query.Where("hour_timestamp BETWEEN ? AND ?", start.Unix(), end.Unix())
  436. case !start.IsZero():
  437. query = query.Where("hour_timestamp >= ?", start.Unix())
  438. case !end.IsZero():
  439. query = query.Where("hour_timestamp <= ?", end.Unix())
  440. }
  441. err := query.
  442. Select(
  443. field + " as value, SUM(request_count) as request_count, SUM(used_amount) as used_amount",
  444. ).
  445. Group(field).
  446. Find(&results).Error
  447. if err != nil {
  448. return nil, err
  449. }
  450. slices.SortFunc(results, func(a, b Result) int {
  451. if a.UsedAmount != b.UsedAmount {
  452. return cmp.Compare(b.UsedAmount, a.UsedAmount)
  453. }
  454. if a.RequestCount != b.RequestCount {
  455. return cmp.Compare(b.RequestCount, a.RequestCount)
  456. }
  457. return cmp.Compare(a.Value, b.Value)
  458. })
  459. values := make([]T, len(results))
  460. for i, result := range results {
  461. values[i] = result.Value
  462. }
  463. return values, nil
  464. }
  465. type ChartData struct {
  466. Timestamp int64 `json:"timestamp"`
  467. UsedAmount float64 `json:"used_amount"`
  468. TotalTimeMilliseconds int64 `json:"total_time_milliseconds"`
  469. TotalTTFBMilliseconds int64 `json:"total_ttfb_milliseconds"`
  470. Count
  471. Usage
  472. }
  473. type DashboardResponse struct {
  474. ChartData []ChartData `json:"chart_data"`
  475. TotalTimeMilliseconds int64 `json:"total_time_milliseconds"`
  476. TotalTTFBMilliseconds int64 `json:"total_ttfb_milliseconds"`
  477. RPM int64 `json:"rpm"`
  478. TPM int64 `json:"tpm"`
  479. MaxRPM int64 `json:"max_rpm"`
  480. MaxTPM int64 `json:"max_tpm"`
  481. UsedAmount float64 `json:"used_amount"`
  482. TotalCount int64 `json:"total_count"` // use Count.RequestCount instead
  483. Count
  484. Usage
  485. Channels []int `json:"channels,omitempty"`
  486. Models []string `json:"models,omitempty"`
  487. }
  488. type GroupDashboardResponse struct {
  489. DashboardResponse
  490. TokenNames []string `json:"token_names"`
  491. }
  492. type TimeSpanType string
  493. const (
  494. TimeSpanMinute TimeSpanType = "minute"
  495. TimeSpanHour TimeSpanType = "hour"
  496. TimeSpanDay TimeSpanType = "day"
  497. TimeSpanMonth TimeSpanType = "month"
  498. )
  499. func aggregateDataToSpan(
  500. data []ChartData,
  501. timeSpan TimeSpanType,
  502. timezone *time.Location,
  503. ) []ChartData {
  504. dataMap := make(map[int64]ChartData)
  505. if timezone == nil {
  506. timezone = time.Local
  507. }
  508. for _, data := range data {
  509. // Convert timestamp to time in the specified timezone
  510. t := time.Unix(data.Timestamp, 0).In(timezone)
  511. // Get the start of the day in the specified timezone
  512. var timestamp int64
  513. switch timeSpan {
  514. case TimeSpanMonth:
  515. startOfMonth := time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, timezone)
  516. timestamp = startOfMonth.Unix()
  517. case TimeSpanDay:
  518. startOfDay := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, timezone)
  519. timestamp = startOfDay.Unix()
  520. case TimeSpanHour:
  521. startOfHour := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), 0, 0, 0, timezone)
  522. timestamp = startOfHour.Unix()
  523. case TimeSpanMinute:
  524. startOfHour := time.Date(
  525. t.Year(),
  526. t.Month(),
  527. t.Day(),
  528. t.Hour(),
  529. t.Minute(),
  530. 0,
  531. 0,
  532. timezone,
  533. )
  534. timestamp = startOfHour.Unix()
  535. }
  536. currentData, exists := dataMap[timestamp]
  537. if !exists {
  538. currentData = ChartData{
  539. Timestamp: timestamp,
  540. }
  541. }
  542. currentData.Count.Add(data.Count)
  543. currentData.Usage.Add(data.Usage)
  544. currentData.TotalTimeMilliseconds += data.TotalTimeMilliseconds
  545. currentData.TotalTTFBMilliseconds += data.TotalTTFBMilliseconds
  546. currentData.UsedAmount = decimal.
  547. NewFromFloat(currentData.UsedAmount).
  548. Add(decimal.NewFromFloat(data.UsedAmount)).
  549. InexactFloat64()
  550. dataMap[timestamp] = currentData
  551. }
  552. result := make([]ChartData, 0, len(dataMap))
  553. for _, data := range dataMap {
  554. result = append(result, data)
  555. }
  556. return result
  557. }
  558. func sumDashboardResponse(chartData []ChartData) DashboardResponse {
  559. dashboardResponse := DashboardResponse{
  560. ChartData: chartData,
  561. }
  562. usedAmount := decimal.NewFromFloat(0)
  563. for _, data := range chartData {
  564. dashboardResponse.Count.Add(data.Count)
  565. dashboardResponse.TotalCount = dashboardResponse.RequestCount
  566. dashboardResponse.Usage.Add(data.Usage)
  567. dashboardResponse.TotalTimeMilliseconds += data.TotalTimeMilliseconds
  568. dashboardResponse.TotalTTFBMilliseconds += data.TotalTTFBMilliseconds
  569. usedAmount = usedAmount.Add(decimal.NewFromFloat(data.UsedAmount))
  570. dashboardResponse.UsedAmount = decimal.
  571. NewFromFloat(dashboardResponse.UsedAmount).
  572. Add(decimal.NewFromFloat(data.UsedAmount)).
  573. InexactFloat64()
  574. }
  575. dashboardResponse.UsedAmount = usedAmount.InexactFloat64()
  576. return dashboardResponse
  577. }
  578. func GetDashboardData(
  579. start,
  580. end time.Time,
  581. modelName string,
  582. channelID int,
  583. timeSpan TimeSpanType,
  584. timezone *time.Location,
  585. ) (*DashboardResponse, error) {
  586. if timeSpan == TimeSpanMinute {
  587. return getDashboardDataMinute(start, end, modelName, channelID, timeSpan, timezone)
  588. }
  589. if end.IsZero() {
  590. end = time.Now()
  591. } else if end.Before(start) {
  592. return nil, errors.New("end time is before start time")
  593. }
  594. var (
  595. chartData []ChartData
  596. channels []int
  597. models []string
  598. )
  599. g := new(errgroup.Group)
  600. g.Go(func() error {
  601. var err error
  602. chartData, err = getChartData(start, end, channelID, modelName, timeSpan, timezone)
  603. return err
  604. })
  605. g.Go(func() error {
  606. var err error
  607. channels, err = GetUsedChannels(start, end)
  608. return err
  609. })
  610. g.Go(func() error {
  611. var err error
  612. models, err = GetUsedModels(start, end)
  613. return err
  614. })
  615. if err := g.Wait(); err != nil {
  616. return nil, err
  617. }
  618. dashboardResponse := sumDashboardResponse(chartData)
  619. dashboardResponse.Channels = channels
  620. dashboardResponse.Models = models
  621. return &dashboardResponse, nil
  622. }
  623. func GetGroupDashboardData(
  624. group string,
  625. start, end time.Time,
  626. tokenName string,
  627. modelName string,
  628. timeSpan TimeSpanType,
  629. timezone *time.Location,
  630. ) (*GroupDashboardResponse, error) {
  631. if timeSpan == TimeSpanMinute {
  632. return getGroupDashboardDataMinute(
  633. group,
  634. start,
  635. end,
  636. tokenName,
  637. modelName,
  638. timeSpan,
  639. timezone,
  640. )
  641. }
  642. if group == "" {
  643. return nil, errors.New("group is required")
  644. }
  645. if end.IsZero() {
  646. end = time.Now()
  647. } else if end.Before(start) {
  648. return nil, errors.New("end time is before start time")
  649. }
  650. var (
  651. chartData []ChartData
  652. tokenNames []string
  653. models []string
  654. )
  655. g := new(errgroup.Group)
  656. g.Go(func() error {
  657. var err error
  658. chartData, err = getGroupChartData(
  659. group,
  660. start,
  661. end,
  662. tokenName,
  663. modelName,
  664. timeSpan,
  665. timezone,
  666. )
  667. return err
  668. })
  669. g.Go(func() error {
  670. var err error
  671. tokenNames, err = GetGroupUsedTokenNames(group, start, end)
  672. return err
  673. })
  674. g.Go(func() error {
  675. var err error
  676. models, err = GetGroupUsedModels(group, tokenName, start, end)
  677. return err
  678. })
  679. if err := g.Wait(); err != nil {
  680. return nil, err
  681. }
  682. dashboardResponse := sumDashboardResponse(chartData)
  683. dashboardResponse.Models = models
  684. return &GroupDashboardResponse{
  685. DashboardResponse: dashboardResponse,
  686. TokenNames: tokenNames,
  687. }, nil
  688. }