urlmapping.go 55 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690
  1. package service
  2. import (
  3. "fmt"
  4. "math"
  5. "net/url"
  6. "sort"
  7. "strconv"
  8. "strings"
  9. "sync"
  10. "time"
  11. "go-admin/app/observe/models"
  12. "go-admin/app/observe/models/query"
  13. "go-admin/app/observe/service/dto"
  14. cDto "go-admin/common/dto"
  15. "go-admin/common/prometheus"
  16. "go-admin/config"
  17. "go-admin/utils"
  18. "github.com/pkg/errors"
  19. "golang.org/x/text/cases"
  20. "golang.org/x/text/language"
  21. "gorm.io/gorm"
  22. )
  23. type UrlMapping struct {
  24. // service.Service
  25. utils.OtService
  26. }
  27. // GetPage 获取UrlMapping列表
  28. func (e *UrlMapping) GetPage(req *dto.UrlMappingGetPageReq, resp *[]dto.UrlMappingListResp, count *int64) error {
  29. req.CheckFilling(time.Minute * 5)
  30. urlMappings := []models.UrlMapping{}
  31. level := 1
  32. if req.Level != "" {
  33. var err error
  34. if level, err = strconv.Atoi(req.Level); err != nil {
  35. level = 1
  36. }
  37. }
  38. appAlias := ""
  39. e.Orm.Model(&models.App{}).Where("id", req.AppId).Pluck("alias", &appAlias)
  40. if appAlias == "" {
  41. return errors.New(fmt.Sprintf("未查询到应用别名: %s", req.AppId))
  42. }
  43. req.Level = strconv.Itoa(level)
  44. db := e.Orm.Model(&models.UrlMapping{}).
  45. Where("app_id", req.AppId).
  46. Where("level=? OR (level<? AND is_perfect_match=1)", level, level).
  47. Scopes(cDto.Paginate(req.GetPageSize(), req.GetPageIndex()))
  48. if req.ServiceName != "" {
  49. db.Where("service_name", req.ServiceName)
  50. }
  51. if req.Url != "" {
  52. db.Where("url LIKE ?", fmt.Sprintf("%%%s%%", req.Url))
  53. }
  54. err := db.Find(&urlMappings).Limit(-1).Offset(-1).Count(count).Error
  55. if err != nil {
  56. if err == gorm.ErrRecordNotFound {
  57. return nil
  58. }
  59. return errors.Wrap(err, "获取收藏的业务映射失败")
  60. }
  61. svcMap, err := query.NewService().ServiceMap(appAlias)
  62. if err != nil {
  63. return err
  64. }
  65. wg := sync.WaitGroup{}
  66. *resp = make([]dto.UrlMappingListResp, len(urlMappings))
  67. for i, um := range urlMappings {
  68. (*resp)[i] = dto.UrlMappingListResp{
  69. Id: int(um.ID),
  70. AppId: strconv.Itoa(int(um.AppId)),
  71. AppAlias: um.AppAlias,
  72. Name: um.Name,
  73. Method: um.Method,
  74. Url: um.Url,
  75. Level: strconv.Itoa(int(um.Level)),
  76. IsPerfectMatch: strconv.Itoa(int(um.IsPerfectMatch)),
  77. Type: strconv.Itoa(int(um.Type)),
  78. Module: um.Module,
  79. Summary: um.Summary,
  80. Favor: strconv.Itoa(int(um.Favor)),
  81. ServiceName: um.ServiceName,
  82. ServiceNameCN: svcMap[um.ServiceName],
  83. }
  84. wg.Add(1)
  85. if config.ExtConfig.ClickhouseMetrics {
  86. go e.urlMappingStatsFromClickhouse(&wg, req, &((*resp)[i]))
  87. } else {
  88. go e.urlMappingStats(&wg, req, &((*resp)[i]))
  89. }
  90. }
  91. wg.Wait()
  92. // return e.genUrlMappingListResp(urlMappings, req, resp)
  93. return nil
  94. }
  95. func (s *UrlMapping) urlMappingStatsFromClickhouse(wg *sync.WaitGroup, req *dto.UrlMappingGetPageReq, item *dto.UrlMappingListResp) {
  96. defer wg.Done()
  97. urlPrefix := fmt.Sprintf("%s%%", item.Url)
  98. db := s.ChOrm.Model(&models.TracesURL{}).
  99. Where("AppAlias=? and ServiceName=? and Method=? and Route like ?", item.AppAlias, item.ServiceName, item.Method, urlPrefix).
  100. Where("Timestamp>toDateTime(?) and Timestamp<toDateTime(?)", req.StartTime, req.EndTime)
  101. fields := []string{
  102. "count() as Total",
  103. "max(Duration) as MaxDuration",
  104. "avg(Duration) as AvgDuration",
  105. "sum(if(StatusCode>=400, 1, 0)) as ErrorNum",
  106. "quantile(0.5)(Duration) as P50Duration",
  107. "quantile(0.90)(Duration) as P90Duration",
  108. "quantile(0.99)(Duration) as P99Duration",
  109. }
  110. row := struct {
  111. Total int64
  112. MaxDuration int64
  113. ErrorNum int64
  114. AvgDuration float64
  115. P50Duration float64
  116. P90Duration float64
  117. P99Duration float64
  118. }{}
  119. err := db.Select(fields).Find(&row).Error
  120. if err != nil {
  121. return
  122. }
  123. if row.Total == 0 {
  124. item.UrlMappingBaseStats = dto.UrlMappingBaseStats{}
  125. item.DurationStats = dto.DurationStats{
  126. Time: []string{},
  127. P50: []float64{},
  128. P90: []float64{},
  129. P99: []float64{},
  130. }
  131. return
  132. }
  133. mins := time.Unix(req.EndTime, 0).Sub(time.Unix(req.StartTime, 0)).Minutes()
  134. timeField := "formatDateTime(toStartOfMinute(Timestamp), '%F %H:%i', 'PRC') as StartTime"
  135. if req.EndTime-req.StartTime > 60*60 {
  136. timeField = "formatDateTime(toStartOfFiveMinutes(Timestamp), '%F %H:%i', 'PRC') as StartTime"
  137. }
  138. fields = []string{
  139. timeField,
  140. "quantile(0.5)(Duration) as P50Duration",
  141. "quantile(0.90)(Duration) as P90Duration",
  142. "quantile(0.99)(Duration) as P99Duration",
  143. }
  144. quantiles := []struct {
  145. StartTime string
  146. P50Duration float64
  147. P90Duration float64
  148. P99Duration float64
  149. }{}
  150. if err := db.Select(fields).Group(timeField).Order(fmt.Sprintf("%s asc", timeField)).Find(&quantiles).Error; err != nil {
  151. return
  152. }
  153. item.Total = row.Total
  154. item.ErrorNum = row.ErrorNum
  155. item.ErrorRate = float64(row.ErrorNum) / float64(row.Total)
  156. item.Rpm = math.Round(float64(row.Total)/mins*100) / 100
  157. item.Max = math.Round(float64(row.MaxDuration)/1e6*100) / 100
  158. item.Avg = math.Round(float64(row.AvgDuration)/1e6*100) / 100
  159. item.P50 = math.Round(float64(row.P50Duration)/1e6*100) / 100
  160. item.P90 = math.Round(float64(row.P90Duration)/1e6*100) / 100
  161. item.P99 = math.Round(float64(row.P99Duration)/1e6*100) / 100
  162. item.DurationStats = dto.DurationStats{
  163. Time: make([]string, len(quantiles)),
  164. P50: make([]float64, len(quantiles)),
  165. P90: make([]float64, len(quantiles)),
  166. P99: make([]float64, len(quantiles)),
  167. }
  168. for i, quantile := range quantiles {
  169. item.DurationStats.Time[i] = quantile.StartTime
  170. item.DurationStats.P50[i] = math.Round(quantile.P50Duration/1e6*100) / 100
  171. item.DurationStats.P90[i] = math.Round(quantile.P90Duration/1e6*100) / 100
  172. item.DurationStats.P99[i] = math.Round(quantile.P99Duration/1e6*100) / 100
  173. }
  174. }
  175. func (s *UrlMapping) urlMappingStats(wg *sync.WaitGroup, req *dto.UrlMappingGetPageReq, item *dto.UrlMappingListResp) {
  176. defer wg.Done()
  177. wg2 := sync.WaitGroup{}
  178. metric := "observe_server_duration_milliseconds"
  179. labels := map[string]string{
  180. "url_level": item.Level,
  181. "url_is_perfect_match": item.IsPerfectMatch,
  182. "url_method": item.Method,
  183. "url_prefix": item.Url,
  184. "service_name": item.ServiceName,
  185. "app_alias": item.AppAlias,
  186. }
  187. ts := time.Unix(req.EndTime, 0)
  188. mins := time.Unix(req.EndTime, 0).Sub(time.Unix(req.StartTime, 0)).Minutes()
  189. hist := prometheus.NewHistogram(&wg2, metric, labels, ts, int64(mins))
  190. var total int64
  191. var avg, max, p50, p90, p99, errRate float64
  192. times := []string{}
  193. p50s, p90s, p99s := []float64{}, []float64{}, []float64{}
  194. wg2.Add(10)
  195. go hist.Total(&total)
  196. go hist.Avg(&avg)
  197. go hist.ErrorRate(&errRate)
  198. go hist.Quantile(0.5, &p50)
  199. go hist.Quantile(0.9, &p90)
  200. go hist.Quantile(0.99, &p99)
  201. go hist.Quantile(0.999, &max) // 如果使用1, 会读到桶边界
  202. go hist.QuantileMinutes(0.5, &[]string{}, &p50s)
  203. go hist.QuantileMinutes(0.9, &[]string{}, &p90s)
  204. go hist.QuantileMinutes(0.99, &times, &p99s)
  205. wg2.Wait()
  206. item.Max = math.Round(max*100) / 100
  207. item.Avg = math.Round(avg*100) / 100
  208. item.Total = total
  209. item.ErrorRate = math.Round(errRate*100) / 100
  210. item.ErrorNum = int64(float64(total) * errRate)
  211. item.Rpm = math.Round(float64(total)/mins*100) / 100
  212. item.P50 = math.Round(p50*100) / 100
  213. item.P90 = math.Round(p90*100) / 100
  214. item.P99 = math.Round(p99*100) / 100
  215. item.DurationStats = dto.DurationStats{
  216. P50: p50s,
  217. P90: p90s,
  218. P99: p99s,
  219. Time: times,
  220. }
  221. }
  222. func (e *UrlMapping) GetFavors(req *dto.UrlMappingGetFavorsReq, resp *[]dto.UrlMappingListResp) error {
  223. urlMappings := []models.UrlMapping{}
  224. err := e.Orm.Model(&models.UrlMapping{}).Where("app_id=? AND favor=?", req.AppId, 1).Limit(5).Find(&urlMappings).Error
  225. if err != nil {
  226. if err == gorm.ErrRecordNotFound {
  227. return nil
  228. }
  229. return errors.Wrap(err, "获取收藏的业务映射失败")
  230. }
  231. return e.genUrlMappingListResp(urlMappings, req, resp)
  232. }
  233. func (e *UrlMapping) genUrlMappingListResp(
  234. urlMappings []models.UrlMapping,
  235. a any,
  236. resp *[]dto.UrlMappingListResp) error {
  237. *resp = make([]dto.UrlMappingListResp, len(urlMappings))
  238. kinds := make([]string, len(urlMappings))
  239. for i, um := range urlMappings {
  240. kinds[i] = um.Url
  241. (*resp)[i] = dto.UrlMappingListResp{
  242. Id: int(um.ID),
  243. AppId: strconv.Itoa(int(um.AppId)),
  244. Name: um.Name,
  245. Method: um.Method,
  246. Url: um.Url,
  247. Type: strconv.Itoa(int(um.Type)),
  248. Module: um.Module,
  249. Summary: um.Summary,
  250. Favor: strconv.Itoa(int(um.Favor)),
  251. ServiceName: um.ServiceName,
  252. }
  253. }
  254. var err error
  255. req := new(dto.UrlMappingGetFavorsReq)
  256. switch r := a.(type) {
  257. case *dto.UrlMappingGetFavorsReq:
  258. req = r
  259. case *dto.UrlMappingGetPageReq:
  260. req = &dto.UrlMappingGetFavorsReq{
  261. AppId: r.AppId,
  262. TimeRange: models.TimeRange{
  263. StartTime: r.StartTime,
  264. EndTime: r.EndTime,
  265. },
  266. }
  267. default:
  268. return errors.Errorf("参数req非法")
  269. }
  270. baseStats := []dto.UrlMappingBaseStats{}
  271. baseStatsMap := map[string]dto.UrlMappingBaseStats{}
  272. err = e.ChOrm.Debug().Model(&models.TracesURL{}).
  273. Select([]string{
  274. "Kind",
  275. "COUNT() AS Total",
  276. "SUM(IF(StatusCode>=400, 1, 0)) AS ErrorNum",
  277. "round(MAX(Duration)/1e6, 2) AS Max",
  278. "round(MIN(Duration)/1e6, 2) AS Min",
  279. "round(AVG(Duration)/1e6, 2) AS Avg",
  280. }).Where("AppId", req.AppId).Where("Kind IN ?", kinds).
  281. Where("Timestamp>=toDateTime(?) AND Timestamp<toDateTime(?)", req.StartTime, req.EndTime).
  282. Group("Kind").Find(&baseStats).Error
  283. if err != nil && err != gorm.ErrRecordNotFound {
  284. return errors.Wrap(err, "基础统计数据获取失败")
  285. }
  286. for _, bs := range baseStats {
  287. baseStatsMap[bs.Kind] = bs
  288. }
  289. // 获取metrics相关数据
  290. type ds struct {
  291. Kind string
  292. StartTimeUnix time.Time
  293. TimeUnix time.Time
  294. Count int64
  295. Sum float64
  296. BucketCounts []uint64
  297. ExplicitBounds []float64
  298. Min float64
  299. Max float64
  300. }
  301. data := []ds{}
  302. db := e.ChOrm.Debug().Select("Attributes['url.kind'] AS Kind, StartTimeUnix, TimeUnix, Count, Sum, BucketCounts, ExplicitBounds, Min, Max").
  303. Table(models.TableNameMetricsHistogram).Where("Attributes['app.id']=?", req.AppId).Where("Kind IN ?", kinds)
  304. db.Where("TimeUnix>=? AND TimeUnix<?", req.StartTime, req.EndTime)
  305. if err := db.Order("Kind ASC, TimeUnix ASC").Find(&data).Error; err != nil {
  306. return errors.Wrap(err, "获取统计数据失败")
  307. }
  308. e.Log.Info("Get Page Service Clickhouse Metrics.")
  309. urlMap := map[string]dto.UrlMappingBaseStats{}
  310. statMap := map[string][]dto.DurationStat{}
  311. for _, item := range data {
  312. qv := Quantiles(item.Count, item.BucketCounts, item.ExplicitBounds, item.Max, []float64{0.5, 0.9, 0.99})
  313. base := dto.UrlMappingBaseStats{ // 由于已经按Timestamp排序,最后一个即为最后的结果
  314. Kind: item.Kind,
  315. P50: qv[0],
  316. P90: qv[1],
  317. P99: qv[2],
  318. }
  319. urlMap[item.Kind] = base
  320. if _, ok := statMap[item.Kind]; !ok {
  321. statMap[item.Kind] = []dto.DurationStat{}
  322. }
  323. statMap[item.Kind] = append(statMap[item.Kind], dto.DurationStat{
  324. Time: item.TimeUnix.Format("2006-01-02 15:04"),
  325. P50: qv[0],
  326. P90: qv[1],
  327. P99: qv[2],
  328. })
  329. }
  330. mins := time.Unix(req.EndTime, 0).Sub(time.Unix(req.StartTime, 0)).Minutes()
  331. for kind, item := range urlMap {
  332. if st, ok := baseStatsMap[kind]; ok {
  333. item.Total = st.Total
  334. item.Max = st.Max
  335. item.Min = st.Min
  336. item.Avg = st.Avg
  337. item.ErrorNum = st.ErrorNum
  338. item.ErrorRate = st.ErrorRate
  339. item.Rpm = float64(item.Total) / mins
  340. urlMap[kind] = item
  341. }
  342. }
  343. // 整合数据
  344. for i := range *resp {
  345. kind := (*resp)[i].Url
  346. baseStat := urlMap[kind]
  347. (*resp)[i].UrlMappingBaseStats = baseStat
  348. (*resp)[i].DurationStats = dto.DurationStats{}
  349. for _, stat := range statMap[kind] {
  350. (*resp)[i].DurationStats.Time = append((*resp)[i].DurationStats.Time, stat.Time)
  351. (*resp)[i].DurationStats.P50 = append((*resp)[i].DurationStats.P50, stat.P50)
  352. (*resp)[i].DurationStats.P90 = append((*resp)[i].DurationStats.P90, stat.P90)
  353. (*resp)[i].DurationStats.P99 = append((*resp)[i].DurationStats.P99, stat.P99)
  354. }
  355. }
  356. return nil
  357. }
  358. func Quantiles(total int64, bucketCounts []uint64, explicitBounds []float64, max float64, qs []float64) (qv []float64) {
  359. qt := make([]float64, len(qs))
  360. qv = make([]float64, len(qs))
  361. for i := range qs {
  362. qt[i] = float64(total) * qs[i]
  363. }
  364. sum := float64(0)
  365. for i := 1; i < len(bucketCounts); i++ { // bucketCounts第0个值代表(-Inf, 0],此值永远取不到
  366. cnt := float64(bucketCounts[i])
  367. prevSum := sum
  368. sum += cnt
  369. for j, n := range qt {
  370. if qv[j] == 0 && sum >= n {
  371. if i < len(explicitBounds) {
  372. qv[j] = explicitBounds[i-1] + (explicitBounds[i]-explicitBounds[i-1])*(n-prevSum)/(sum-prevSum)
  373. } else {
  374. qv[j] = explicitBounds[len(explicitBounds)-1] + (max-explicitBounds[len(explicitBounds)-1])*(n-prevSum)/(sum-prevSum)
  375. }
  376. }
  377. }
  378. }
  379. return
  380. }
  381. // Get 获取UrlMapping对象
  382. func (e *UrlMapping) Get(c *dto.UrlMappingGetReq, result *dto.UrlMappingGetResp) error {
  383. var data models.UrlMapping
  384. model := new(models.UrlMapping)
  385. err := e.Orm.Model(&data).
  386. First(model, c.GetId()).Error
  387. if err != nil {
  388. e.Log.Errorf("db error:%s", err)
  389. return err
  390. }
  391. *result = dto.UrlMappingGetResp{
  392. // Id: model.Id,
  393. AppId: strconv.Itoa(int(model.AppId)),
  394. Name: model.Name,
  395. Url: model.Url,
  396. Type: strconv.Itoa(int(model.Type)),
  397. Module: model.Module,
  398. Summary: model.Summary,
  399. Favor: strconv.Itoa(int(model.Favor)),
  400. }
  401. return nil
  402. }
  403. // Insert 创建UrlMapping对象
  404. func (e *UrlMapping) Insert(c *dto.UrlMappingInsertReq) error {
  405. var err error
  406. var data models.UrlMapping
  407. cnt := int64(0)
  408. err = e.Orm.Model(&models.UrlMapping{}).Where("app_id", c.AppId).Where("url", c.Url).Count(&cnt).Error
  409. if err != nil {
  410. e.Log.Errorf("查询app_id:%d下url:%s的个数失败:%s", c.AppId, c.Url, err)
  411. return err
  412. }
  413. if cnt > 0 {
  414. e.Log.Errorf("该app_id:%s下已经存在相同的url:%s", c.AppId, c.Url)
  415. return fmt.Errorf("该app_id:%s下已经存在相同的url:%s", c.AppId, c.Url)
  416. }
  417. c.Generate(&data)
  418. err = e.Orm.Create(&data).Error
  419. if err != nil {
  420. e.Log.Errorf("创建url-mapping失败:%s", err)
  421. return err
  422. }
  423. return nil
  424. }
  425. // Update 修改UrlMapping对象
  426. func (e *UrlMapping) Update(c *dto.UrlMappingUpdateReq) error {
  427. var err error
  428. var data = models.UrlMapping{}
  429. err = e.Orm.First(&data, c.GetId()).Error
  430. if err != nil {
  431. if err == gorm.ErrRecordNotFound {
  432. return fmt.Errorf("更新目标不存在, id: %d", c.GetId())
  433. }
  434. return err
  435. }
  436. c.Generate(&data)
  437. // 去掉以下逻辑,因为app_id+url不再唯一
  438. // var cnt int64
  439. // err = e.Orm.Model(&models.UrlMapping{}).
  440. // Where("id != ? AND app_id = ? AND url = ?", c.GetId(), c.AppId, c.Url).Count(&cnt).Error
  441. // if err != nil {
  442. // e.Log.Errorf("查询app_id:%d下url:%s的个数失败:%s", c.AppId, c.Url, err)
  443. // return err
  444. // }
  445. // if cnt > 0 {
  446. // e.Log.Errorf("该app_id:%s下已经存在相同的url:%s", c.AppId, c.Url)
  447. // return fmt.Errorf("该app_id:%s下已经存在相同的url:%s", c.AppId, c.Url)
  448. // }
  449. db := e.Orm.Save(&data)
  450. if err = db.Error; err != nil {
  451. e.Log.Errorf("UrlMappingService Save error:%s \r\n", err)
  452. return err
  453. }
  454. return nil
  455. }
  456. // Favor 收藏/取消收藏
  457. func (e *UrlMapping) Favor(c *dto.UrlMappingFavorReq) error {
  458. var data = models.UrlMapping{}
  459. if err := e.Orm.First(&data, c.Id).Error; err != nil {
  460. return errors.Wrap(err, "获取urlmapping数据失败")
  461. }
  462. // 不需要再有5个的限制了
  463. // if c.Favor == "1" {
  464. // var count int64
  465. // err = e.Orm.Model(&models.UrlMapping{}).Where("app_id", data.AppId).Where("favor", 1).Count(&count).Error
  466. // if err != nil {
  467. // return errors.Wrap(err, "获取收藏数量失败")
  468. // }
  469. // if count >= 5 {
  470. // return errors.New("最多只能收藏5个业务映射")
  471. // }
  472. // }
  473. if err := e.Orm.Model(&data).Update("favor", c.Favor).Error; err != nil {
  474. msg := "收藏失败"
  475. if c.Favor == "0" {
  476. msg = "取消" + msg
  477. }
  478. return errors.Wrap(err, msg)
  479. }
  480. return nil
  481. }
  482. // Remove 删除UrlMapping
  483. func (e *UrlMapping) Remove(d *dto.UrlMappingDeleteReq) error {
  484. var data models.UrlMapping
  485. db := e.Orm.Model(&data).Delete(&data, d.GetIds())
  486. if err := db.Error; err != nil {
  487. e.Log.Errorf("Service RemoveUrlMapping error:%s \r\n", err)
  488. return err
  489. }
  490. return nil
  491. }
  492. // GetDetail 获取指定业务映射指定区间内的全部请求
  493. func (e *UrlMapping) GetDetail(c *dto.UrlMappingGetDetailReq, result *[]dto.UrlMappingGetDetailResp, count *int64) (err error) {
  494. // var data models.UrlMapping
  495. // err = e.Orm.Model(&models.UrlMapping{}).First(&data, c.Id).Count(count).Error
  496. // if err != nil {
  497. // msg := "获取业务映射失败"
  498. // e.Log.Error(fmt.Errorf("%s, id: %d, err: %s", msg, c.Id, err))
  499. // return fmt.Errorf(msg)
  500. // }
  501. appAlias := ""
  502. if err := e.Orm.Model(&models.App{}).Where("id", c.AppId).Pluck("alias", &appAlias).Error; err != nil {
  503. return errors.Wrap(err, "获取app别名失败")
  504. }
  505. traceUrls := []models.TracesURL{}
  506. if c.Route == "" {
  507. c.Route = c.Kind // 兼容旧逻辑
  508. }
  509. db := e.ChOrm.Debug().Model(&models.TracesURL{}).
  510. Scopes(cDto.Paginate(c.GetPageSize(), c.GetPageIndex())).
  511. Where("AppAlias", appAlias).Where("Route", c.Route)
  512. if c.Method != "" {
  513. db.Where("Method", c.Method)
  514. }
  515. if c.ServiceName != "" {
  516. db.Where("ServiceName", c.ServiceName)
  517. }
  518. if c.OnlyError == "1" || c.OnlyException { // only error 后面删除
  519. db.Where("StatusCode>=?", 400)
  520. }
  521. if c.MinDuration > 0 {
  522. db.Where("Duration>=?", c.MinDuration*float64(time.Millisecond))
  523. }
  524. if c.MaxDuration > 0 {
  525. db.Where("Duration<=?", c.MaxDuration*float64(time.Millisecond))
  526. }
  527. if c.StartTime == c.EndTime && c.StartTime > 0 { // 对于start_time == end_time的情况,取前后一分钟的数据
  528. db.Where("Timestamp>=toDateTime(?) AND Timestamp<=toDateTime(?)", c.StartTime-60, c.EndTime+60)
  529. } else {
  530. c.CheckFilling(time.Hour * 48) // 最大取2天,加快速度
  531. db.Where("Timestamp>=toDateTime(?) AND Timestamp<=toDateTime(?)", c.StartTime, c.EndTime)
  532. }
  533. c.SortInfo.Field = cases.Title(language.Und).String(c.SortInfo.Field)
  534. db.Order(c.SortInfo.OrderBy([]string{"Timestamp", "Duration"}, "Duration", "DESC"))
  535. err = db.Find(&traceUrls).Limit(-1).Offset(-1).Count(count).Error
  536. if err != nil && err != gorm.ErrRecordNotFound {
  537. return errors.Wrap(err, "获取业务映射详情失败")
  538. }
  539. *result = make([]dto.UrlMappingGetDetailResp, len(traceUrls))
  540. // spanIds := make([]string, len(*result))
  541. for i, item := range traceUrls {
  542. // spanIds[i] = item.SpanID
  543. item.Duration /= 1e6
  544. (*result)[i] = dto.UrlMappingGetDetailResp{
  545. Datetime: item.Timestamp.Local().Format(time.DateTime),
  546. Timestamp: item.Timestamp.Unix(),
  547. Route: item.Route,
  548. Target: item.Target,
  549. URL: item.URL,
  550. Flavor: item.Flavor,
  551. Host: item.Host,
  552. Method: item.Method,
  553. StatusCode: item.StatusCode,
  554. Message: item.Message,
  555. TraceID: item.TraceID,
  556. SpanID: item.SpanID,
  557. ServiceName: item.ServiceName,
  558. Duration: item.Duration,
  559. }
  560. }
  561. // spanInfos := []models.Trace{}
  562. // err = e.ChOrm.Table(models.TableNameTrace).Where("SpanId IN ?", spanIds).Find(&spanInfos).Error
  563. // if err != nil {
  564. // msg := "获取业务映射对应的span信息失败"
  565. // e.Log.Errorf("%s: %s", msg, err)
  566. // return fmt.Errorf(msg)
  567. // }
  568. // spanInfoMap := make(map[string]models.Trace, len(spanInfos))
  569. // for _, span := range spanInfos {
  570. // spanInfoMap[span.SpanID] = span
  571. // }
  572. // for i, item := range *result {
  573. // if spanInfo, ok := spanInfoMap[item.SpanID]; ok { // otel_traces 表中的数据不全,有的spanId找不到
  574. // (*result)[i].SpanDetail = &spanInfo
  575. // }
  576. // }
  577. return
  578. }
  579. // Sankey 桑基图
  580. func (e *UrlMapping) Sankey(c *dto.UrlMappingSankeyReq, result *dto.UrlMappingSankeyResp) (err error) {
  581. links1 := []dto.SankeyLink{}
  582. c.CheckFilling(time.Minute * 5)
  583. var alias string
  584. if err := e.Orm.Model(&models.App{}).Where("id", c.AppId).Pluck("alias", &alias).Error; err != nil {
  585. return errors.Wrap(err, "appid查找失败")
  586. }
  587. var model models.TracesURL
  588. scope := func(db *gorm.DB) *gorm.DB {
  589. db.Where("AppAlias", alias)
  590. db.Where("Timestamp >= ? AND Timestamp < ?", c.StartTime, c.EndTime)
  591. return db
  592. }
  593. if c.Percentile == 0 { // 默认百分位数 90%
  594. c.Percentile = 0.9
  595. }
  596. // 获取指定分位数对应的duration
  597. var percentile float64 = 0
  598. fieldsTmpl := "quantile(%.2f)(Duration) AS Q"
  599. err = e.ChOrm.Model(&models.TracesURL{}).Select(fmt.Sprintf(fieldsTmpl, c.Percentile)).Scopes(scope).Pluck("Q", &percentile).Error
  600. if err != nil {
  601. return errors.Wrap(err, "获取时延分位数失败")
  602. }
  603. pcond := func(db *gorm.DB) *gorm.DB {
  604. db.Where("Duration>?", percentile)
  605. return db
  606. }
  607. if err = e.ChOrm.Debug().Model(&models.TracesURL{}).
  608. // anyLast取最后遇到的值,结果可能不确定,在AppName频繁变化的情况下,可能会取到不同的值
  609. Select("anyLast(AppName) AS Source, CONCAT('Name:', Name) AS Target, COUNT() AS Value").
  610. Scopes(scope, pcond).
  611. Group("Target").
  612. Find(&links1).Error; err != nil {
  613. return errors.Wrap(err, "获取links数据失败1")
  614. }
  615. links2 := []dto.SankeyLink{}
  616. if err = e.ChOrm.Model(&model).
  617. Select("CONCAT('Name:', Name) AS Source, CONCAT('Route:', Route) AS Target, COUNT() AS Value").
  618. Scopes(scope, pcond).
  619. Group("Source, Target").
  620. Find(&links2).Error; err != nil {
  621. return errors.Wrap(err, "获取links数据失败2")
  622. }
  623. // links3 := []dto.SankeyLink{}
  624. // t1 := e.ChOrm.Model(&model).
  625. // Select("CONCAT('Route:', Route) AS Source, CONCAT('Target:', Target) AS Target, COUNT() AS Value").
  626. // Scopes(scope).Group("Source, Target")
  627. // t2 := e.ChOrm.Table("(?) AS t1", t1).
  628. // Select("Source, Target, Value, ROW_NUMBER() OVER(PARTITION BY Source ORDER BY Value DESC) AS RowNum")
  629. // if err = e.ChOrm.Table("(?) AS t2", t2).
  630. // Select("Source, Target, Value").
  631. // Where("RowNum<=?", 10). // 暂定最多只取前10条
  632. // Find(&links3).Error; err != nil {
  633. // return errors.Wrap(err, "获取links数据失败3")
  634. // }
  635. links4mp := []map[string]any{}
  636. if err = e.ChOrm.Model(&model).
  637. // 暂定每500毫秒一个区间
  638. // Select("CONCAT('Target:', `Target`) AS `Source`, CEIL(Duration/1e6/500)*500 AS Target2, COUNT() Value").
  639. Select("CONCAT('Route:', `Route`) AS `Source`, CEIL(Duration/1e6/500)*500 AS Target, COUNT() Value").
  640. Scopes(scope, pcond).
  641. // Group("Source, Target2").
  642. // Order("Source, Target2").
  643. Group("Source, Target").
  644. Order("Source, Target").
  645. Find(&links4mp).Error; err != nil {
  646. return errors.Wrap(err, "获取links数据失败4")
  647. }
  648. links4 := make([]dto.SankeyLink, len(links4mp))
  649. var prevTarget, curTarget string
  650. var prevSource, curSource string
  651. var k int
  652. for i, link := range links4mp {
  653. curSource = link["Source"].(string)
  654. // curTarget = strconv.Itoa(int(link["Target2"].(float64)))
  655. curTarget = link["Target"].(string)
  656. target := ""
  657. if prevSource != curSource {
  658. k = 0
  659. }
  660. if k == 0 {
  661. target = fmt.Sprintf("%sms以下", curTarget)
  662. prevTarget = curTarget
  663. } else if k > 9 {
  664. target = fmt.Sprintf("%sms以上", prevTarget)
  665. } else {
  666. target = fmt.Sprintf("%s~%sms", prevTarget, curTarget)
  667. prevTarget = curTarget
  668. }
  669. prevSource = curSource
  670. k++
  671. cur := dto.SankeyLink{
  672. Source: link["Source"].(string),
  673. Target: target,
  674. Value: int(link["Value"].(uint64)),
  675. }
  676. links4[i] = cur
  677. }
  678. // links := make([]dto.SankeyLink, 0, len(links1)+len(links2)+len(links3)+len(links4))
  679. links := make([]dto.SankeyLink, 0, len(links1)+len(links2)+len(links4))
  680. links = append(links, links1...)
  681. links = append(links, links2...)
  682. // links = append(links, links3...)
  683. links = append(links, links4...)
  684. if len(links) == 0 {
  685. result.Links = []dto.SankeyLink{}
  686. result.Nodes = []dto.SankeyNode{}
  687. return nil
  688. }
  689. result.Links = links
  690. nodeNameMap := map[string]struct{}{
  691. links[0].Source: {},
  692. }
  693. for _, link := range links {
  694. nodeNameMap[link.Target] = struct{}{}
  695. }
  696. nodes := make([]dto.SankeyNode, 0, len(nodeNameMap))
  697. for name := range nodeNameMap {
  698. nodes = append(nodes, dto.SankeyNode{
  699. Name: name,
  700. })
  701. }
  702. result.Nodes = nodes
  703. return nil
  704. }
  705. func (s *UrlMapping) QualityFromClickhouse(req dto.UrlMappingQualityReq, resp *dto.UrlMappingQualityResp) error {
  706. req.CheckFilling(time.Hour)
  707. type BucketCount struct {
  708. Bucket string
  709. Total int64
  710. }
  711. appAlias := ""
  712. if err := s.Orm.Model(&models.App{}).Where("id", req.AppId).Pluck("alias", &appAlias).Error; err != nil {
  713. return errors.Wrap(err, "获取应用别名失败")
  714. }
  715. genBucketCounts := func(start, end int64) ([]BucketCount, error) {
  716. bucketCounts := []BucketCount{}
  717. db := s.ChOrm.Model(&models.TracesURL{}).
  718. Where("AppAlias=?", appAlias).
  719. Where("Timestamp>toDateTime(?) and Timestamp<toDateTime(?)", start, end)
  720. err := db.Select(`IF(Duration/1e6<=5, '5', IF(Duration/1e6<=10, '10', IF(Duration/1e6<=25, '25', IF(Duration/1e6<=50, '50', IF(Duration/1e6<=75, '75',
  721. IF(Duration/1e6<=100, '100', IF(Duration/1e6<=250, '250', IF(Duration/1e6<=500, '500', IF(Duration/1e6<=750, '750', IF(Duration/1e6<=1000, '1000',
  722. IF(Duration/1e6<=2500, '2500', IF(Duration/1e6<=5000, '5000', IF(Duration/1e6<=7500, '7500', IF(Duration/1e6<=10000, '10000', '10000+'))))
  723. ))))) ))))) as Bucket, Count() as Total`).Group("Bucket").Order("Bucket asc").Find(&bucketCounts).Error
  724. if err != nil {
  725. return bucketCounts, errors.Wrap(err, "获取buckets数据失败")
  726. }
  727. sort.Slice(bucketCounts, func(i, j int) bool {
  728. bi, err := strconv.Atoi(bucketCounts[i].Bucket)
  729. if err != nil {
  730. return false
  731. }
  732. bj, err := strconv.Atoi(bucketCounts[j].Bucket)
  733. if err != nil {
  734. return true
  735. }
  736. return bi < bj
  737. })
  738. return bucketCounts, nil
  739. }
  740. type PxxDuration struct {
  741. P50Duration float64
  742. P90Duration float64
  743. P99Duration float64
  744. }
  745. genQuantile := func(start, end int64) (PxxDuration, error) {
  746. row := struct {
  747. P50Duration float64
  748. P90Duration float64
  749. P99Duration float64
  750. }{}
  751. db := s.ChOrm.Model(&models.TracesURL{}).
  752. Where("AppAlias=?", appAlias).
  753. Where("Timestamp>toDateTime(?) and Timestamp<toDateTime(?)", start, end)
  754. err := db.Select([]string{
  755. "quantile(0.5)(Duration)/1e6 as P50Duration",
  756. "quantile(0.90)(Duration)/1e6 as P90Duration",
  757. "quantile(0.99)(Duration)/1e6 as P99Duration",
  758. }).Find(&row).Error
  759. return row, err
  760. }
  761. secs := req.EndTime - req.StartTime
  762. bc1, err := genBucketCounts(req.StartTime, req.EndTime)
  763. if err != nil {
  764. return err
  765. }
  766. bc2, err := genBucketCounts(req.StartTime-secs, req.EndTime-secs)
  767. if err != nil {
  768. return err
  769. }
  770. resp.Buckets = make([]string, 0, len(bc1))
  771. counts1, counts2 := []float64{}, []float64{}
  772. i, j := 0, 0
  773. for i < len(bc1) && j < len(bc2) {
  774. if bc1[i].Bucket == bc2[i].Bucket {
  775. resp.Buckets = append(resp.Buckets, bc1[i].Bucket)
  776. counts1 = append(counts1, float64(bc1[i].Total))
  777. counts2 = append(counts2, float64(bc2[j].Total))
  778. i++
  779. j++
  780. } else if bc1[i].Bucket < bc2[j].Bucket {
  781. resp.Buckets = append(resp.Buckets, bc1[i].Bucket)
  782. counts1 = append(counts1, float64(bc1[i].Total))
  783. i++
  784. } else {
  785. resp.Buckets = append(resp.Buckets, bc2[j].Bucket)
  786. counts2 = append(counts2, float64(bc2[j].Total))
  787. j++
  788. }
  789. }
  790. for i < len(bc1) {
  791. resp.Buckets = append(resp.Buckets, bc1[i].Bucket)
  792. i++
  793. }
  794. for j < len(bc2) {
  795. resp.Buckets = append(resp.Buckets, bc2[j].Bucket)
  796. j++
  797. }
  798. buckets := []float64{}
  799. for _, bucket := range resp.Buckets {
  800. if num, err := strconv.Atoi(bucket); err == nil {
  801. buckets = append(buckets, float64(num))
  802. }
  803. }
  804. pxx1, err := genQuantile(req.StartTime, req.EndTime)
  805. if err != nil {
  806. return nil
  807. }
  808. pxx2, err := genQuantile(req.StartTime-secs, req.EndTime-secs)
  809. if err != nil {
  810. return nil
  811. }
  812. resp.Current = dto.CountsQuantileIndexes{
  813. Counts: counts1,
  814. QuantileIndexes: map[string]int{
  815. "p50": sort.SearchFloat64s(buckets, pxx1.P50Duration),
  816. "p90": sort.SearchFloat64s(buckets, pxx1.P90Duration),
  817. "p99": sort.SearchFloat64s(buckets, pxx1.P99Duration),
  818. },
  819. }
  820. resp.Previous = dto.CountsQuantileIndexes{
  821. Counts: counts2,
  822. QuantileIndexes: map[string]int{
  823. "p50": sort.SearchFloat64s(buckets, pxx2.P50Duration),
  824. "p90": sort.SearchFloat64s(buckets, pxx2.P90Duration),
  825. "p99": sort.SearchFloat64s(buckets, pxx2.P99Duration),
  826. },
  827. }
  828. return nil
  829. }
  830. // 接口质量统计,即请求时长相关分布
  831. func (s *UrlMapping) Quality(req dto.UrlMappingQualityReq, resp *dto.UrlMappingQualityResp) error {
  832. wg := sync.WaitGroup{}
  833. req.CheckFilling(time.Hour)
  834. cur, prev := time.Unix(req.EndTime, 0), time.Unix(req.StartTime, 0)
  835. mins := int64(cur.Sub(prev).Minutes())
  836. name := "observe_server_duration_milliseconds"
  837. labels := map[string]string{"app_id": req.AppId}
  838. if req.UrlType != "" {
  839. labels["url_type"] = req.UrlType
  840. }
  841. buckets, counts := []float64{}, []float64{}
  842. var p50, p90, p99 [2]float64
  843. buckets2, counts2 := []float64{}, []float64{}
  844. wg.Add(8)
  845. hist := prometheus.NewHistogram(&wg, name, labels, cur, mins)
  846. hist2 := prometheus.NewHistogram(&wg, name, labels, prev, mins)
  847. go hist.BucketCounts(&buckets, &counts)
  848. go hist.Quantile(0.5, &p50[0])
  849. go hist.Quantile(0.9, &p90[0])
  850. go hist.Quantile(0.99, &p99[0])
  851. go hist2.BucketCounts(&buckets2, &counts2)
  852. go hist2.Quantile(0.5, &p50[1])
  853. go hist2.Quantile(0.9, &p90[1])
  854. go hist2.Quantile(0.99, &p99[1])
  855. wg.Wait()
  856. resp.Buckets = make([]string, len(buckets))
  857. for i, bucket := range buckets {
  858. resp.Buckets[i] = fmt.Sprintf("%.0f", bucket)
  859. }
  860. resp.Current = dto.CountsQuantileIndexes{
  861. Counts: counts,
  862. QuantileIndexes: map[string]int{
  863. "p50": sort.SearchFloat64s(buckets, p50[0]),
  864. "p90": sort.SearchFloat64s(buckets, p90[0]),
  865. "p99": sort.SearchFloat64s(buckets, p99[0]),
  866. },
  867. }
  868. resp.Previous = dto.CountsQuantileIndexes{
  869. Counts: counts2,
  870. QuantileIndexes: map[string]int{
  871. "p50": sort.SearchFloat64s(buckets, p50[1]),
  872. "p90": sort.SearchFloat64s(buckets, p90[1]),
  873. "p99": sort.SearchFloat64s(buckets, p99[1]),
  874. },
  875. }
  876. return nil
  877. }
  878. func (s *UrlMapping) ErrorsFromClickhouse(req dto.UrlMappingErrorsReq, resp *dto.UrlMappingErrorsResp) error {
  879. startTime, endTime := req.TimeBase-req.TimeValue*24*60*60, req.TimeBase
  880. timeField := "formatDateTime(toStartOfDay(Timestamp), '%F', 'PRC') as StartTime"
  881. if req.TimeType == "hour" {
  882. startTime = req.TimeBase - req.TimeValue*60*60
  883. timeField = "formatDateTime(toStartOfHour(Timestamp), '%F %H:%i', 'PRC') as StartTime"
  884. }
  885. appAlias := ""
  886. if err := s.Orm.Model(&models.App{}).Where("id", req.AppId).Pluck("alias", &appAlias).Error; err != nil {
  887. return errors.Wrap(err, "获取应用别名失败")
  888. }
  889. list := []struct {
  890. StartTime string
  891. Total int64
  892. }{}
  893. if err := s.ChOrm.Model(&models.TracesURL{}).
  894. Select(fmt.Sprintf("%s, count() as Total", timeField)).
  895. Where("AppAlias", appAlias).
  896. Where("Timestamp>toDateTime(?) and Timestamp<toDateTime(?)", startTime, endTime).
  897. Where("StatusCode>=400").Group("StartTime").Order("StartTime asc").Find(&list).Error; err != nil {
  898. return errors.Wrap(err, "查询错误分布数据失败")
  899. }
  900. resp.Times = make([]string, len(list))
  901. resp.Totals = make([]int64, len(list))
  902. for i, item := range list {
  903. resp.Times[i] = item.StartTime
  904. resp.Totals[i] = item.Total
  905. }
  906. return nil
  907. }
  908. // 接口错误相关统计
  909. func (s *UrlMapping) Errors(req dto.UrlMappingErrorsReq, resp *dto.UrlMappingErrorsResp) error {
  910. labels := map[string]string{
  911. "app_id": req.AppId,
  912. "error": "true",
  913. }
  914. if req.UrlType != "" {
  915. labels["url_type"] = req.UrlType
  916. }
  917. mins := int64(0)
  918. unit := "m"
  919. if req.TimeType == "day" {
  920. mins = int64(req.TimeValue) * 24 * 60
  921. unit = "d"
  922. } else if req.TimeType == "hour" {
  923. unit = "h"
  924. mins = int64(req.TimeValue) * 60
  925. } else {
  926. return errors.New("无效的time type")
  927. }
  928. name := "observe_server_duration_milliseconds"
  929. hist := prometheus.NewHistogram(nil, name, labels, time.Unix(int64(req.TimeBase), 0), mins)
  930. times := []time.Time{}
  931. totals := []float64{}
  932. err := hist.TotalUnits(unit, &times, &totals)
  933. if err != nil {
  934. return err
  935. }
  936. resp.Times = make([]string, len(times))
  937. resp.Totals = make([]int64, len(totals))
  938. for i, t := range times {
  939. resp.Times[i] = t.Format(time.DateOnly)
  940. if req.TimeType == "hour" {
  941. resp.Times[i] = t.Format("2006-01-02 15:00:00")
  942. }
  943. resp.Totals[i] = int64(totals[i])
  944. }
  945. return nil
  946. }
  947. func (s *UrlMapping) SlowTop(req *dto.UrlMappingSlowTopReq, resp *[]dto.UrlMappingSlowTopResp) error {
  948. req.CheckFilling(time.Hour)
  949. if req.EndTime-req.StartTime > 3600 {
  950. req.StartTime = req.EndTime - 3600
  951. }
  952. app := models.App{}
  953. if err := s.Orm.Model(&models.App{}).Where("id", req.AppId).Find(&app).Error; err != nil {
  954. return errors.Wrap(err, "appid非法")
  955. }
  956. if err := s.ChOrm.Model(&models.TracesURL{}).
  957. Select("AppAlias, ServiceName, IF(Route!='', Route, Path) AS Route, Method, SUM(IF(StatusCode>=400, 1, 0))/COUNT() ErrorRate, AVG(Duration)/1e6 Duration").
  958. Where("AppAlias", app.Alias).
  959. Where("Timestamp>=toDateTime(?) AND Timestamp<toDateTime(?) and Route!=''", req.StartTime, req.EndTime).
  960. Group("AppAlias, ServiceName, Route, Method").
  961. Order("Duration DESC").Limit(int(req.Limit)).
  962. Find(resp).Error; err != nil {
  963. return errors.Wrap(err, "获取trace url失败")
  964. }
  965. svcMap, err := query.NewService().ServiceMap(app.Alias)
  966. if err != nil {
  967. return err
  968. }
  969. wg := sync.WaitGroup{}
  970. wg.Add(len(*resp) * 2)
  971. for i := range *resp {
  972. item := (*resp)[i]
  973. // (*resp)[i].ErrorRate = (*resp)[i].ErrorRate
  974. (*resp)[i].Duration = math.Round(item.Duration*100) / 100
  975. (*resp)[i].ServiceNameCN = svcMap[item.ServiceName]
  976. go s.routeName(&wg, item.ServiceName, item.Method, item.Route, &((*resp)[i].Name))
  977. if config.ExtConfig.ClickhouseMetrics {
  978. go s.slowtopStatsFromClickhouse(&wg, req, &((*resp)[i]))
  979. } else {
  980. go s.slowtopStats(&wg, req, &((*resp)[i]))
  981. }
  982. }
  983. wg.Wait()
  984. return nil
  985. }
  986. func (s *UrlMapping) slowtopStatsFromClickhouse(wg *sync.WaitGroup, req *dto.UrlMappingSlowTopReq, item *dto.UrlMappingSlowTopResp) {
  987. defer wg.Done()
  988. timeField := "formatDateTime(toStartOfMinute(Timestamp), '%F %H:%i', 'PRC') as StartTime"
  989. if req.EndTime-req.StartTime >= 3600 {
  990. timeField = "formatDateTime(toStartOfFiveMinutes(Timestamp), '%F %H:%i', 'PRC') as StartTime"
  991. }
  992. fields := []string{
  993. timeField,
  994. "quantile(0.5)(Duration)/1e6 as P50Duration",
  995. "quantile(0.90)(Duration)/1e6 as P90Duration",
  996. "quantile(0.99)(Duration)/1e6 as P99Duration",
  997. }
  998. quantiles := []struct {
  999. StartTime string
  1000. P50Duration float64
  1001. P90Duration float64
  1002. P99Duration float64
  1003. }{}
  1004. if err := s.ChOrm.Model(&models.TracesURL{}).Select(fields).
  1005. Where("Route", item.Route).
  1006. Where("Method", item.Method).
  1007. Where("ServiceName", item.ServiceName).
  1008. Where("AppAlias", item.AppAlias).
  1009. Where("Timestamp>toDateTime(?) and Timestamp<toDateTime(?)", req.StartTime, req.EndTime).
  1010. Group(timeField).Order(fmt.Sprintf("%s asc", timeField)).Find(&quantiles).Error; err != nil {
  1011. return
  1012. }
  1013. item.DurationStats = dto.DurationStats{
  1014. Time: make([]string, 0, len(quantiles)),
  1015. P50: make([]float64, 0, len(quantiles)),
  1016. P90: make([]float64, 0, len(quantiles)),
  1017. P99: make([]float64, 0, len(quantiles)),
  1018. }
  1019. for _, quantile := range quantiles {
  1020. item.DurationStats.Time = append(item.DurationStats.Time, quantile.StartTime)
  1021. item.DurationStats.P50 = append(item.DurationStats.P50, math.Round(quantile.P50Duration*100)/100)
  1022. item.DurationStats.P90 = append(item.DurationStats.P90, math.Round(quantile.P90Duration*100)/100)
  1023. item.DurationStats.P99 = append(item.DurationStats.P99, math.Round(quantile.P99Duration*100)/100)
  1024. }
  1025. }
  1026. func (s *UrlMapping) slowtopStats(wg *sync.WaitGroup, req *dto.UrlMappingSlowTopReq, item *dto.UrlMappingSlowTopResp) {
  1027. defer wg.Done()
  1028. metric := "observe_server_duration_milliseconds"
  1029. ts := time.Unix(req.EndTime, 0)
  1030. mins := time.Unix(req.EndTime, 0).Sub(time.Unix(req.StartTime, 0)).Minutes()
  1031. wg2 := sync.WaitGroup{}
  1032. labels := map[string]string{
  1033. "url_prefix": item.Route,
  1034. "url_method": item.Method,
  1035. "service_name": item.ServiceName,
  1036. "app_alias": item.AppAlias,
  1037. "url_is_perfect_match": "1",
  1038. }
  1039. hist := prometheus.NewHistogram(&wg2, metric, labels, ts, int64(mins))
  1040. p50s, p90s, p99s := []float64{}, []float64{}, []float64{}
  1041. times := []string{}
  1042. wg2.Add(3)
  1043. go hist.QuantileMinutes(0.5, &[]string{}, &p50s)
  1044. go hist.QuantileMinutes(0.9, &[]string{}, &p90s)
  1045. go hist.QuantileMinutes(0.99, &times, &p99s)
  1046. wg2.Wait()
  1047. item.DurationStats = dto.DurationStats{
  1048. Time: times,
  1049. P50: p50s,
  1050. P90: p90s,
  1051. P99: p99s,
  1052. }
  1053. }
  1054. func (s *UrlMapping) routeName(wg *sync.WaitGroup, serviceName, method, route string, routeName *string) error {
  1055. defer wg.Done()
  1056. if err := s.Orm.Model(&models.UrlMapping{}).
  1057. Where("service_name", serviceName).
  1058. Where("method", method).
  1059. Where("url", route).Pluck("name", routeName).Error; err != nil {
  1060. return errors.Wrap(err, "获取url mapping名称失败")
  1061. }
  1062. return nil
  1063. }
  1064. func (s *UrlMapping) SubDigits(req *dto.UrlMappingSubDigitsReq, resp *dto.UrlMappingSubDigitsResp) error {
  1065. req.CheckFilling(time.Minute * 5)
  1066. um := models.UrlMapping{}
  1067. if err := s.Orm.Where("id", req.ID).Find(&um).Error; err != nil {
  1068. return errors.Wrap(err, "获取url mapping信息失败")
  1069. }
  1070. digit := struct {
  1071. Total int64
  1072. ErrorNum int64
  1073. MedianDuration float64
  1074. MaxDuration float64
  1075. }{}
  1076. db := s.ChOrm.Model(&models.TracesURL{}).
  1077. Select("count() as Total, sum(if(StatusCode>=400, 1, 0)) as ErrorNum, quantile(0.5)(Duration)/1e6 as MedianDuration, max(Duration)/1e6 as MaxDuration").
  1078. // Where("Route like ?", um.Url+"%").Where("Method", um.Method).
  1079. // Where("ServiceName", um.ServiceName).
  1080. Where("Timestamp>=? and Timestamp<?", req.StartTime, req.EndTime)
  1081. if req.ID > 0 {
  1082. db.Where("AppAlias=? and ServiceName=?", um.AppAlias, um.ServiceName)
  1083. db.Where("(Route=? OR Path=?) and Method=?", um.Url, um.Url, um.Method)
  1084. } else if req.AppAlias != "" {
  1085. db.Where("AppAlias", req.AppAlias)
  1086. }
  1087. if err := db.Find(&digit).Error; err != nil {
  1088. return errors.Wrap(err, "查询数字视图数据失败")
  1089. }
  1090. if math.IsNaN(digit.MedianDuration) {
  1091. digit.MedianDuration = 0
  1092. }
  1093. resp.Total = digit.Total
  1094. if digit.Total > 0 {
  1095. resp.ErrorRate = float64(digit.ErrorNum) / float64(digit.Total)
  1096. }
  1097. mins := float64(req.EndTime-req.StartTime) / 60
  1098. resp.QPM = math.Round(float64(resp.Total)/mins*100) / 100
  1099. resp.MedianDuration = math.Round(digit.MedianDuration*100) / 100
  1100. resp.MaxDuration = math.Round(digit.MaxDuration*100) / 100
  1101. // wg := sync.WaitGroup{}
  1102. // metric := "observe_server_duration_milliseconds"
  1103. // labels := map[string]string{
  1104. // "url_level": strconv.Itoa(int(um.Level)),
  1105. // "url_method": um.Method,
  1106. // "url_prefix": um.Url,
  1107. // "service_name": um.ServiceName,
  1108. // "app_alias": um.AppAlias,
  1109. // }
  1110. // ts := time.Unix(req.EndTime, 0)
  1111. // mins := time.Unix(req.EndTime, 0).Sub(time.Unix(req.StartTime, 0)).Minutes()
  1112. // hist := prometheus.NewHistogram(&wg, metric, labels, ts, int64(mins))
  1113. // var errRate float64
  1114. // wg.Add(4)
  1115. // go hist.Total(&resp.Total)
  1116. // go hist.ErrorRate(&errRate)
  1117. // go hist.Quantile(0.90, &resp.MaxDuration)
  1118. // go hist.Quantile(0.5, &resp.MedianDuration)
  1119. // wg.Wait()
  1120. // resp.ErrorRate = fmt.Sprintf("%.2f%%", errRate*float64(100))
  1121. // resp.QPM = math.Round(float64(resp.Total)/mins*100) / 100
  1122. // resp.MedianDuration = math.Round(resp.MedianDuration*100) / 100
  1123. // resp.MaxDuration = math.Round(resp.MaxDuration*100) / 100
  1124. return nil
  1125. }
  1126. func (s *UrlMapping) SubList(req *dto.UrlMappingSubListReq, resp *[]dto.UrlMappingSubListResp, count *int64) error {
  1127. req.CheckFilling(time.Hour)
  1128. // um := models.UrlMapping{}
  1129. // if err := s.Orm.Where("id", req.ID).Find(&um).Error; err != nil {
  1130. // return errors.Wrap(err, "获取url mapping信息失败")
  1131. // }
  1132. list := []models.UrlMapping{}
  1133. db := s.Orm.Select("id, name, url, method, service_name, app_alias, level, favor, module, summary, type").
  1134. Where("app_alias=?", req.AppAlias).
  1135. // Where("url like ? AND is_perfect_match=?", um.Url+"%", 1)
  1136. Where("is_perfect_match=?", 1).
  1137. Where("url!=?", "")
  1138. var serviceNames []string
  1139. if req.ServiceName != "" {
  1140. serviceNames = strings.Split(req.ServiceName, ",")
  1141. if len(serviceNames) > 0 {
  1142. db.Where("service_name in ?", serviceNames)
  1143. }
  1144. }
  1145. urlMappingIds := []string{}
  1146. if req.BizHash != "" {
  1147. err := s.Orm.Model(&models.BizEdge{}).
  1148. Joins("inner join ot_biz_node on ot_biz_node.id=ot_biz_edge.source or ot_biz_node.id=ot_biz_edge.target").
  1149. Where("ot_biz_edge.biz_hash=? and external_id>0", req.BizHash).
  1150. Pluck("external_id", &urlMappingIds).Error
  1151. if err != nil {
  1152. return errors.Wrap(err, "获取extend id失败")
  1153. }
  1154. }
  1155. if len(urlMappingIds) > 0 {
  1156. db.Where("id in ?", urlMappingIds)
  1157. }
  1158. if req.Url != "" {
  1159. db.Where("url like ?", "%"+req.Url+"%")
  1160. }
  1161. if err := db.
  1162. Scopes(cDto.Paginate(req.GetPageSize(), req.GetPageIndex())).
  1163. Find(&list).Offset(-1).Limit(-1).Count(count).Error; err != nil {
  1164. return errors.Wrap(err, "获取接口映射子列表失败")
  1165. }
  1166. *resp = make([]dto.UrlMappingSubListResp, len(list))
  1167. svcMap, err := query.NewService().ServiceMap(req.AppAlias)
  1168. if err != nil {
  1169. return errors.Wrap(err, "获取service映射失败")
  1170. }
  1171. wg := sync.WaitGroup{}
  1172. wg.Add(len(list))
  1173. for i := range list {
  1174. item := list[i]
  1175. (*resp)[i] = dto.UrlMappingSubListResp{
  1176. ID: item.ID,
  1177. Name: item.Name,
  1178. Route: item.Url,
  1179. Method: item.Method,
  1180. ServiceName: item.ServiceName,
  1181. ServiceNameCN: svcMap[item.ServiceName],
  1182. AppAlias: item.AppAlias,
  1183. Favor: int(item.Favor),
  1184. Module: item.Module,
  1185. Summary: item.Summary,
  1186. }
  1187. if config.ExtConfig.ClickhouseMetrics {
  1188. go s.subStatsFromClickhouse(&wg, req, &(*resp)[i])
  1189. } else {
  1190. go s.SubStats(&wg, &item, req, &(*resp)[i])
  1191. }
  1192. }
  1193. wg.Wait()
  1194. return nil
  1195. }
  1196. func (s *UrlMapping) subStatsFromClickhouse(wg *sync.WaitGroup, req *dto.UrlMappingSubListReq, item *dto.UrlMappingSubListResp) {
  1197. defer wg.Done()
  1198. db := s.ChOrm.Model(&models.TracesURL{}).
  1199. Where("AppAlias=? and ServiceName=? and Method=? and Route=?", item.AppAlias, item.ServiceName, item.Method, item.Route).
  1200. Where("Timestamp>toDateTime(?) and Timestamp<toDateTime(?)", req.StartTime, req.EndTime)
  1201. fields := []string{
  1202. "count() as Total",
  1203. "max(Duration) as MaxDuration",
  1204. "avg(Duration) as AvgDuration",
  1205. "sum(if(StatusCode>=400, 1, 0)) as ErrorNum",
  1206. "quantile(0.5)(Duration) as P50Duration",
  1207. "quantile(0.90)(Duration) as P90Duration",
  1208. "quantile(0.99)(Duration) as P99Duration",
  1209. }
  1210. row := struct {
  1211. Total int64
  1212. MaxDuration int64
  1213. ErrorNum int64
  1214. AvgDuration float64
  1215. P50Duration float64
  1216. P90Duration float64
  1217. P99Duration float64
  1218. }{}
  1219. err := db.Select(fields).Find(&row).Error
  1220. if err != nil {
  1221. return
  1222. }
  1223. if row.Total == 0 {
  1224. item.DurationStats = dto.DurationStats{
  1225. Time: []string{},
  1226. P50: []float64{},
  1227. P90: []float64{},
  1228. P99: []float64{},
  1229. }
  1230. return
  1231. }
  1232. timeField := "formatDateTime(toStartOfMinute(Timestamp), '%F %H:%i', 'PRC') as StartTime"
  1233. if req.EndTime-req.StartTime >= 60*60 {
  1234. timeField = "formatDateTime(toStartOfFiveMinutes(Timestamp), '%F %H:%i', 'PRC') as StartTime"
  1235. }
  1236. fields = []string{
  1237. timeField,
  1238. "quantile(0.5)(Duration) as P50Duration",
  1239. "quantile(0.90)(Duration) as P90Duration",
  1240. "quantile(0.99)(Duration) as P99Duration",
  1241. }
  1242. quantiles := []struct {
  1243. StartTime string
  1244. P50Duration float64
  1245. P90Duration float64
  1246. P99Duration float64
  1247. }{}
  1248. if err := db.Select(fields).Group(timeField).Order(fmt.Sprintf("%s asc", timeField)).Find(&quantiles).Error; err != nil {
  1249. return
  1250. }
  1251. item.Total = row.Total
  1252. item.DurationStats = dto.DurationStats{
  1253. Time: make([]string, len(quantiles)),
  1254. P50: make([]float64, len(quantiles)),
  1255. P90: make([]float64, len(quantiles)),
  1256. P99: make([]float64, len(quantiles)),
  1257. }
  1258. for i, quantile := range quantiles {
  1259. item.DurationStats.Time[i] = quantile.StartTime
  1260. item.DurationStats.P50[i] = math.Round(quantile.P50Duration/1e6*100) / 100
  1261. item.DurationStats.P90[i] = math.Round(quantile.P90Duration/1e6*100) / 100
  1262. item.DurationStats.P99[i] = math.Round(quantile.P99Duration/1e6*100) / 100
  1263. }
  1264. }
  1265. func (s *UrlMapping) SubStats(wg *sync.WaitGroup, um *models.UrlMapping, req *dto.UrlMappingSubListReq, resp *dto.UrlMappingSubListResp) error {
  1266. defer wg.Done()
  1267. wg2 := sync.WaitGroup{}
  1268. metric := "observe_server_duration_milliseconds"
  1269. labels := map[string]string{
  1270. "url_level": strconv.Itoa(int(um.Level)),
  1271. "url_is_perfect_match": "1",
  1272. "url_method": um.Method,
  1273. "url_prefix": um.Url,
  1274. "service_name": um.ServiceName,
  1275. "app_alias": um.AppAlias,
  1276. }
  1277. ts := time.Unix(req.EndTime, 0)
  1278. mins := time.Unix(req.EndTime, 0).Sub(time.Unix(req.StartTime, 0)).Minutes()
  1279. hist := prometheus.NewHistogram(&wg2, metric, labels, ts, int64(mins))
  1280. wg2.Add(5)
  1281. var errorRate float64
  1282. go hist.Total(&resp.Total)
  1283. go hist.ErrorRate(&errorRate)
  1284. times, p50s, p90s, p99s := []string{}, []float64{}, []float64{}, []float64{}
  1285. go hist.QuantileMinutes(0.5, &[]string{}, &p50s)
  1286. go hist.QuantileMinutes(0.9, &[]string{}, &p90s)
  1287. go hist.QuantileMinutes(0.99, &times, &p99s)
  1288. wg2.Wait()
  1289. resp.ErrorRate = errorRate
  1290. resp.DurationStats = dto.DurationStats{
  1291. Time: times,
  1292. P50: p50s,
  1293. P90: p90s,
  1294. P99: p99s,
  1295. }
  1296. return nil
  1297. }
  1298. func (s *UrlMapping) ChartQPS(req *dto.UrlMappingChartReq, resp *dto.UrlMappingChartResp) error {
  1299. wg := sync.WaitGroup{}
  1300. hist, timeUnit, timeList := s.chartCommon(req, &wg)
  1301. group := ""
  1302. if req.Type == "status_trend" {
  1303. group = "status_code"
  1304. }
  1305. times, values := map[string][]string{}, map[string][]float64{}
  1306. wg.Add(1)
  1307. go hist.QPS(timeUnit, group, &times, &values)
  1308. wg.Wait()
  1309. resp.Times = timeList
  1310. if group == "status_code" {
  1311. resp.Values = map[string][]float64{
  1312. "2XX": make([]float64, len(timeList)),
  1313. "3XX": make([]float64, len(timeList)),
  1314. "4XX": make([]float64, len(timeList)),
  1315. "5XX": make([]float64, len(timeList)),
  1316. }
  1317. for key := range times {
  1318. if key < "200" {
  1319. continue
  1320. }
  1321. k := fmt.Sprintf("%cXX", key[0])
  1322. for i, j := 0, 0; j < len(times[key]) && i < len(timeList); i++ {
  1323. if timeList[i] == times[key][j] {
  1324. resp.Values[k][i] += math.Round(values[key][j]*60*100) / 100 // *60是为了将QPS转为数量
  1325. j++
  1326. }
  1327. // else 此时 times[key]中缺少某一时间
  1328. }
  1329. }
  1330. } else {
  1331. key := ""
  1332. resp.Values = map[string][]float64{
  1333. key: make([]float64, len(timeList)),
  1334. }
  1335. for i, j := 0, 0; j < len(times[key]) && i < len(timeList); i++ {
  1336. if timeList[i] == times[key][j] {
  1337. resp.Values[key][i] += values[key][j]
  1338. j++
  1339. }
  1340. // else 此时 times[key]中缺少某一时间
  1341. }
  1342. }
  1343. return nil
  1344. }
  1345. func (s *UrlMapping) ChartQPSFromClickhouse(req *dto.UrlMappingChartReq, resp *dto.UrlMappingChartResp) error {
  1346. req.CheckFilling(time.Minute * 5)
  1347. timeField := "formatDateTime(toStartOfMinute(Timestamp), '%F %H:%i', 'PRC') as StartTime"
  1348. if req.EndTime-req.StartTime >= 60*60 {
  1349. timeField = "formatDateTime(toStartOfFiveMinutes(Timestamp), '%F %H:%i', 'PRC') as StartTime"
  1350. }
  1351. db := s.ChOrm.Model(&models.TracesURL{}).
  1352. Where("AppAlias", req.AppAlias).
  1353. Where("ServiceName", req.ServiceName).
  1354. Where("Method", req.Method).
  1355. Where("Route=? or Path=?", req.Route, req.Route).
  1356. Where("Timestamp>toDateTime(?) and Timestamp<toDateTime(?)", req.StartTime, req.EndTime)
  1357. if req.Type == "status_trend" {
  1358. list := []struct {
  1359. StartTime string
  1360. StatusCodes string
  1361. Total int64
  1362. }{}
  1363. fields := fmt.Sprintf("%s,if(StatusCode>=500, '5XX', if(StatusCode>=400, '4XX', if(StatusCode>=300, '3XX', '2XX'))) StatusCodes, count() Total", timeField)
  1364. if err := db.Select(fields).
  1365. Group("StartTime, StatusCodes").Order("StartTime asc, StatusCodes asc").Find(&list).Error; err != nil {
  1366. return errors.Wrap(err, "获取QPS相关数据失败")
  1367. }
  1368. resp.Times = []string{}
  1369. resp.Values = map[string][]float64{
  1370. "2XX": {},
  1371. "3XX": {},
  1372. "4XX": {},
  1373. "5XX": {},
  1374. }
  1375. for _, item := range list {
  1376. if len(resp.Times) > 0 {
  1377. if item.StartTime != resp.Times[len(resp.Times)-1] {
  1378. resp.Times = append(resp.Times, item.StartTime)
  1379. }
  1380. } else {
  1381. resp.Times = append(resp.Times, item.StartTime)
  1382. }
  1383. resp.Values[item.StatusCodes] = append(resp.Values[item.StatusCodes], float64(item.Total))
  1384. }
  1385. } else {
  1386. list := []struct {
  1387. StartTime string
  1388. Total int64
  1389. }{}
  1390. if err := db.Select(fmt.Sprintf("%s, count() Total", timeField)).
  1391. Group("StartTime").Order("StartTime asc").Find(&list).Error; err != nil {
  1392. return errors.Wrap(err, "获取QPS相关数据失败")
  1393. }
  1394. resp.Times = []string{}
  1395. resp.Values = map[string][]float64{
  1396. "": {},
  1397. }
  1398. for _, item := range list {
  1399. resp.Times = append(resp.Times, item.StartTime)
  1400. resp.Values[""] = append(resp.Values[""], float64(item.Total))
  1401. }
  1402. }
  1403. return nil
  1404. }
  1405. func (s *UrlMapping) ChartLatencyFromClickhouse(req *dto.UrlMappingChartReq, resp *dto.UrlMappingChartResp) error {
  1406. req.CheckFilling(time.Minute * 5)
  1407. timeField := "formatDateTime(toStartOfMinute(Timestamp), '%F %H:%i', 'PRC') as StartTime"
  1408. if req.EndTime-req.StartTime >= 60*60 {
  1409. timeField = "formatDateTime(toStartOfFiveMinutes(Timestamp), '%F %H:%i', 'PRC') as StartTime"
  1410. }
  1411. fields := []string{
  1412. timeField,
  1413. "quantile(0.5)(Duration)/1e6 as P50Duration",
  1414. "quantile(0.90)(Duration)/1e6 as P90Duration",
  1415. "quantile(0.99)(Duration)/1e6 as P99Duration",
  1416. }
  1417. quantiles := []struct {
  1418. StartTime string
  1419. P50Duration float64
  1420. P90Duration float64
  1421. P99Duration float64
  1422. }{}
  1423. if err := s.ChOrm.Model(&models.TracesURL{}).Select(fields).
  1424. Where("AppAlias", req.AppAlias).
  1425. Where("ServiceName", req.ServiceName).
  1426. Where("Method", req.Method).
  1427. Where("Route=? or Path=?", req.Route, req.Route).
  1428. Where("Timestamp>toDateTime(?) and Timestamp<toDateTime(?)", req.StartTime, req.EndTime).
  1429. Group(timeField).Order(fmt.Sprintf("%s asc", timeField)).Find(&quantiles).Error; err != nil {
  1430. return errors.Wrap(err, "获取分位数数据失败")
  1431. }
  1432. resp.Times = []string{}
  1433. resp.Values = map[string][]float64{
  1434. "P50": {},
  1435. "P90": {},
  1436. "P99": {},
  1437. }
  1438. for _, quantile := range quantiles {
  1439. resp.Times = append(resp.Times, quantile.StartTime)
  1440. resp.Values["P50"] = append(resp.Values["P50"], math.Round(quantile.P50Duration*100)/100)
  1441. resp.Values["P90"] = append(resp.Values["P90"], math.Round(quantile.P50Duration*100)/100)
  1442. resp.Values["P99"] = append(resp.Values["P99"], math.Round(quantile.P50Duration*100)/100)
  1443. }
  1444. return nil
  1445. }
  1446. func (s *UrlMapping) ChartLatency(req *dto.UrlMappingChartReq, resp *dto.UrlMappingChartResp) error {
  1447. wg := sync.WaitGroup{}
  1448. hist, timeUnit, timeList := s.chartCommon(req, &wg)
  1449. times50, times90, times99 := []string{}, []string{}, []string{}
  1450. values50, values90, values99 := []float64{}, []float64{}, []float64{}
  1451. wg.Add(3)
  1452. go hist.QuantileTimeUnit(timeUnit, 0.5, &times50, &values50)
  1453. go hist.QuantileTimeUnit(timeUnit, 0.9, &times90, &values90)
  1454. go hist.QuantileTimeUnit(timeUnit, 0.99, &times99, &values99)
  1455. wg.Wait()
  1456. resp.Times = timeList
  1457. times := map[string][]string{
  1458. "P50": times50,
  1459. "P90": times90,
  1460. "P99": times99,
  1461. }
  1462. values := map[string][]float64{
  1463. "P50": values50,
  1464. "P90": values90,
  1465. "P99": values99,
  1466. }
  1467. resp.Values = map[string][]float64{
  1468. "P50": make([]float64, len(timeList)),
  1469. "P90": make([]float64, len(timeList)),
  1470. "P99": make([]float64, len(timeList)),
  1471. }
  1472. for key := range times {
  1473. for i, j := 0, 0; j < len(times[key]) && i < len(timeList); i++ {
  1474. if timeList[i] == times[key][j] {
  1475. resp.Values[key][i] += values[key][j]
  1476. j++
  1477. }
  1478. // else 此时 times[key]中缺少某一时间
  1479. }
  1480. }
  1481. return nil
  1482. }
  1483. func (s *UrlMapping) chartCommon(req *dto.UrlMappingChartReq, wg *sync.WaitGroup) (prometheus.Histogram, string, []string) {
  1484. metric := "observe_server_duration_milliseconds"
  1485. labels := map[string]string{
  1486. "url_is_perfect_match": "1",
  1487. "app_alias": req.AppAlias,
  1488. }
  1489. if req.ServiceName != "" {
  1490. labels["service_name"] = req.ServiceName
  1491. }
  1492. if req.Method != "" {
  1493. labels["url_method"] = req.Method
  1494. }
  1495. if req.Route != "" {
  1496. labels["url_prefix"] = req.Route
  1497. }
  1498. req.CheckFilling(time.Minute * 5)
  1499. ts := time.Unix(req.EndTime, 0)
  1500. mins := time.Unix(req.EndTime, 0).Sub(time.Unix(req.StartTime, 0)).Minutes()
  1501. fmt.Println("#######-----", mins, req.EndTime, req.StartTime)
  1502. timeUnit := "d"
  1503. layout := time.DateOnly
  1504. diff := 60 * 60 * 24
  1505. startTime := req.StartTime - req.StartTime%int64(diff)
  1506. endTime := req.EndTime - req.EndTime%int64(diff)
  1507. if mins <= 60 {
  1508. timeUnit = "m"
  1509. layout = "2006-01-02 15:04"
  1510. diff = 60
  1511. startTime = req.StartTime - req.StartTime%int64(diff)
  1512. endTime = req.EndTime - req.EndTime%int64(diff)
  1513. ts = time.Unix(endTime, 0)
  1514. } else if mins <= 24*60 {
  1515. timeUnit = "h"
  1516. diff = 60 * 60
  1517. layout = "2006-01-02 15"
  1518. startTime = req.StartTime - req.StartTime%int64(diff)
  1519. endTime = req.EndTime - req.EndTime%int64(diff)
  1520. ts = time.Unix(endTime, 0)
  1521. } else {
  1522. // 兼容 prometheus 特殊情况
  1523. // 当 时间哦 2024-5-27 9:48:45 ~ 2024-5-27 10:48:45, prometheus计算返回的时间是 2024-5-27 9:48到2024-5-27 10:48
  1524. // 当 时间哦 2024-5-27 8:48:45 ~ 2024-5-27 10:48:45, prometheus计算返回的时间是 2024-5-27 8到2024-5-27 10
  1525. // 而当 时间哦 2024-5-21 10:48:45 ~ 2024-5-27 10:48:45, prometheus计算返回的时间确是 2024-5-22 到2024-5-27, 少了一天,所以此处+1440分钟, 多加一天, 让返回结果为 2024-5-21 到2024-5-27
  1526. mins += 1440
  1527. }
  1528. times := []string{}
  1529. for startTime <= endTime {
  1530. times = append(times, time.Unix(startTime, 0).Format(layout))
  1531. startTime += int64(diff)
  1532. }
  1533. return prometheus.NewHistogram(wg, metric, labels, ts, int64(mins)), timeUnit, times
  1534. }
  1535. func (s *UrlMapping) BaseInfo(req *dto.UrlMappingBaseInfoReq, resp *dto.UrlMappingBaseInfoResp) error {
  1536. u, err := url.Parse(req.Url)
  1537. if err != nil {
  1538. return errors.Wrap(err, "非法url")
  1539. }
  1540. db := s.Orm.Model(&models.UrlMapping{}).Where("url", u.Path).Where("is_perfect_match", 1)
  1541. if req.AppAlias != "" {
  1542. db.Where("app_alias", req.AppAlias)
  1543. }
  1544. if req.ServiceName != "" {
  1545. db.Where("service_name", req.ServiceName)
  1546. }
  1547. if req.Method != "" {
  1548. db.Where("method", req.Method)
  1549. }
  1550. um := models.UrlMapping{}
  1551. if err := db.First(&um).Error; err != nil {
  1552. return errors.Wrap(err, "获取url基础信息失败")
  1553. }
  1554. var appName string
  1555. if err := s.Orm.Model(&models.App{}).Where("alias", um.AppAlias).Pluck("name", &appName).Error; err != nil {
  1556. return errors.Wrap(err, "获取应用名称失败")
  1557. }
  1558. var serviceNameCn string
  1559. if err := s.Orm.Model(&models.ServiceNode{}).
  1560. Where("service_name", um.ServiceName).Where("app_alias", um.AppAlias).Pluck("name", &serviceNameCn).Error; err != nil {
  1561. return errors.Wrap(err, "获取应用名称失败")
  1562. }
  1563. // bizNodeId := int64(0)
  1564. // if err := s.Orm.Model(&models.BizNode{}).
  1565. // Where("app_alias", um.AppAlias).Where("service_name", um.ServiceName).Where("span_kind='SPAN_KIND_SERVER'").
  1566. // Where("span_name like ?", "%"+um.Url).Pluck("id", &bizNodeId).Error; err != nil {
  1567. // return errors.Wrap(err, "获取业务结点id失败")
  1568. // }
  1569. // bizId := int64(0)
  1570. // if err := s.Orm.Model(&models.BizEdge{}).Where("source=? or target=?", bizNodeId, bizNodeId).Pluck("biz_id", &bizId).Error; err != nil {
  1571. // return errors.Wrap(err, "获取业务id失败")
  1572. // }
  1573. resp.ID = um.ID
  1574. resp.AppAlias = um.AppAlias
  1575. resp.AppName = appName
  1576. resp.Method = um.Method
  1577. resp.Route = um.Url
  1578. resp.ServiceName = um.ServiceName
  1579. resp.ServiceNameCn = serviceNameCn
  1580. // resp.BizID = bizId
  1581. // resp.BizNodeID = bizNodeId
  1582. return nil
  1583. }
  1584. func (s UrlMapping) Match(req *dto.UrlMappingMatchReq, resp *[]dto.UrlMappingMatchResp, total *int64) error {
  1585. u, err := url.Parse(req.Url)
  1586. if err != nil {
  1587. return errors.Wrap(err, "非法url")
  1588. }
  1589. db := s.Orm.Model(&models.UrlMapping{}).Where("url like ?", fmt.Sprintf("%%%s%%", u.Path)).Where("is_perfect_match", 1)
  1590. if req.AppAlias != "" {
  1591. db.Where("app_alias", req.AppAlias)
  1592. }
  1593. if req.ServiceName != "" {
  1594. db.Where("service_name", req.ServiceName)
  1595. }
  1596. if req.Method != "" {
  1597. db.Where("method", req.Method)
  1598. }
  1599. ums := []models.UrlMapping{}
  1600. if err := db.Scopes(cDto.Paginate(req.GetPageSize(), req.GetPageIndex())).
  1601. Order("level asc").Find(&ums).Limit(-1).Offset(-1).Count(total).Error; err != nil {
  1602. return errors.Wrap(err, "获取url信息失败")
  1603. }
  1604. for _, um := range ums {
  1605. *resp = append(*resp, dto.UrlMappingMatchResp{
  1606. AppAlias: um.AppAlias,
  1607. ServiceName: um.ServiceName,
  1608. Route: um.Url,
  1609. Method: um.Method,
  1610. })
  1611. }
  1612. return nil
  1613. }