httpstats.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package misc
  15. import (
  16. "fmt"
  17. "time"
  18. "yunion.io/x/log"
  19. "yunion.io/x/onecloud/pkg/cloudmon/options"
  20. "yunion.io/x/onecloud/pkg/util/influxdb"
  21. )
  22. type SHttpStats struct {
  23. HttpCode2xx float64 `json:"duration.2XX"`
  24. HttpCode4xx float64 `json:"duration.4XX"`
  25. HttpCode5xx float64 `json:"duration.5XX"`
  26. HitHttpCode2xx int64 `json:"hit.2XX"`
  27. HitHttpCode4xx int64 `json:"hit.4XX"`
  28. HitHttpCode5xx int64 `json:"hit.5XX"`
  29. Method string `json:"method"`
  30. Name string `json:"name"`
  31. Path string `json:"path"`
  32. }
  33. type sApiHttpStats struct {
  34. SHttpStats
  35. Paths []SHttpStats `json:"paths"`
  36. }
  37. func (apiStats *sApiHttpStats) convertSnapshot(now time.Time) *sHttpStatsSnapshot {
  38. snapshot := &sHttpStatsSnapshot{
  39. snapshotAt: now,
  40. stats: map[string]*SHttpStats{},
  41. }
  42. apiStats.SHttpStats.Method = "any"
  43. apiStats.SHttpStats.Path = "any"
  44. apiStats.SHttpStats.Name = "any"
  45. snapshot.stats[getStatsKey(apiStats.SHttpStats.Method, apiStats.SHttpStats.Path, apiStats.SHttpStats.Name)] = &apiStats.SHttpStats
  46. for i := range apiStats.Paths {
  47. pathStats := apiStats.Paths[i]
  48. snapshot.stats[getStatsKey(pathStats.Method, pathStats.Path, pathStats.Name)] = &pathStats
  49. }
  50. return snapshot
  51. }
  52. type sHttpStatsExt struct {
  53. Duration2xx float64 `json:"duration_2xx"`
  54. Duration4xx float64 `json:"duration_4xx"`
  55. Duration5xx float64 `json:"duration_5xx"`
  56. Hit2xx int64 `json:"hit_2xx"`
  57. Hit4xx int64 `json:"hit_4xx"`
  58. Hit5xx int64 `json:"hit_5xx"`
  59. Duration2xxDiff float64 `json:"duration_2xx_diff"`
  60. Duration4xxDiff float64 `json:"duration_4xx_diff"`
  61. Duration5xxDiff float64 `json:"duration_5xx_diff"`
  62. Hit2xxDiff int64 `json:"hit_2xx_diff"`
  63. Hit4xxDiff int64 `json:"hit_4xx_diff"`
  64. Hit5xxDiff int64 `json:"hit_5xx_diff"`
  65. Method string `json:"method"`
  66. Path string `json:"path"`
  67. Name string `json:"name"`
  68. HasDiff bool `json:"has_diff"`
  69. }
  70. func (v sHttpStatsExt) DelayMs2xx() float64 {
  71. if v.Hit2xxDiff > 0 {
  72. return v.Duration2xxDiff / float64(v.Hit2xxDiff)
  73. }
  74. return -1
  75. }
  76. func (v sHttpStatsExt) Qps2xx(interval time.Duration) float64 {
  77. if interval > 0 {
  78. return float64(v.Hit2xxDiff) / interval.Seconds()
  79. }
  80. return -1
  81. }
  82. func (v sHttpStatsExt) DelayMs4xx() float64 {
  83. if v.Hit4xxDiff > 0 {
  84. return v.Duration4xxDiff / float64(v.Hit4xxDiff)
  85. }
  86. return -1
  87. }
  88. func (v sHttpStatsExt) Qps4xx(interval time.Duration) float64 {
  89. if interval > 0 {
  90. return float64(v.Hit4xxDiff) / interval.Seconds()
  91. }
  92. return -1
  93. }
  94. func (v sHttpStatsExt) DelayMs5xx() float64 {
  95. if v.Hit5xxDiff > 0 {
  96. return v.Duration5xxDiff / float64(v.Hit5xxDiff)
  97. }
  98. return -1
  99. }
  100. func (v sHttpStatsExt) Qps5xx(interval time.Duration) float64 {
  101. if interval > 0 {
  102. return float64(v.Hit5xxDiff) / interval.Seconds()
  103. }
  104. return -1
  105. }
  106. func (v sHttpStatsExt) Qps(interval time.Duration) float64 {
  107. if interval > 0 {
  108. return float64(v.Hit2xxDiff+v.Hit4xxDiff+v.Hit5xxDiff) / interval.Seconds()
  109. }
  110. return -1
  111. }
  112. func (v sHttpStatsExt) DelayMs() float64 {
  113. if v.HitDiff() > 0 {
  114. return v.DurationMsDiff() / float64(v.HitDiff())
  115. }
  116. return -1
  117. }
  118. func (v sHttpStatsExt) DurationMs() float64 {
  119. return v.Duration2xx + v.Duration4xx + v.Duration5xx
  120. }
  121. func (v sHttpStatsExt) Hit() int64 {
  122. return v.Hit2xx + v.Hit4xx + v.Hit5xx
  123. }
  124. func (v sHttpStatsExt) DurationMsDiff() float64 {
  125. return v.Duration2xxDiff + v.Duration4xxDiff + v.Duration5xxDiff
  126. }
  127. func (v sHttpStatsExt) HitDiff() int64 {
  128. return v.Hit2xxDiff + v.Hit4xxDiff + v.Hit5xxDiff
  129. }
  130. func (v sHttpStatsExt) Percent2xx() float64 {
  131. if v.DurationMsDiff() > 0 {
  132. return v.Duration2xxDiff * 100 / v.DurationMsDiff()
  133. }
  134. return -1
  135. }
  136. func (v sHttpStatsExt) PercentHit2xx() float64 {
  137. if v.HitDiff() > 0 {
  138. return float64(v.Hit2xxDiff) * 100 / float64(v.HitDiff())
  139. }
  140. return -1
  141. }
  142. func (v sHttpStatsExt) Percent4xx() float64 {
  143. if v.DurationMsDiff() > 0 {
  144. return v.Duration4xxDiff * 100 / v.DurationMsDiff()
  145. }
  146. return -1
  147. }
  148. func (v sHttpStatsExt) PercentHit4xx() float64 {
  149. if v.HitDiff() > 0 {
  150. return float64(v.Hit4xxDiff) * 100 / float64(v.HitDiff())
  151. }
  152. return -1
  153. }
  154. func (v sHttpStatsExt) Percent5xx() float64 {
  155. if v.DurationMsDiff() > 0 {
  156. return v.Duration5xxDiff * 100 / v.DurationMsDiff()
  157. }
  158. return -1
  159. }
  160. func (v sHttpStatsExt) PercentHit5xx() float64 {
  161. if v.HitDiff() > 0 {
  162. return float64(v.Hit5xxDiff) * 100 / float64(v.HitDiff())
  163. }
  164. return -1
  165. }
  166. type sHttpStatsSnapshot struct {
  167. snapshotAt time.Time
  168. stats map[string]*SHttpStats
  169. }
  170. type sHttpStatsDiff struct {
  171. snapshotAt time.Time
  172. interval time.Duration
  173. stats map[string]*sHttpStatsExt
  174. }
  175. func calculateHttpStatsDiff(prevSnap *sHttpStatsSnapshot, nowSnap *sHttpStatsSnapshot) *sHttpStatsDiff {
  176. diff := sHttpStatsDiff{
  177. snapshotAt: nowSnap.snapshotAt,
  178. stats: map[string]*sHttpStatsExt{},
  179. }
  180. if prevSnap != nil {
  181. rootKey := getStatsKey("any", "any", "any")
  182. prevRoot := prevSnap.stats[rootKey]
  183. nowRoot := nowSnap.stats[rootKey]
  184. if prevRoot.HttpCode2xx > nowRoot.HttpCode2xx || prevRoot.HttpCode4xx > nowRoot.HttpCode4xx || prevRoot.HttpCode5xx > nowRoot.HttpCode5xx ||
  185. prevRoot.HitHttpCode2xx > nowRoot.HitHttpCode2xx || prevRoot.HitHttpCode4xx > nowRoot.HitHttpCode4xx || prevRoot.HitHttpCode5xx > nowRoot.HitHttpCode5xx {
  186. // detect a reset, skip this round
  187. prevSnap = nil
  188. }
  189. }
  190. if prevSnap != nil {
  191. diff.interval = nowSnap.snapshotAt.Sub(prevSnap.snapshotAt)
  192. } else {
  193. intvMin := options.Options.CollectServiceMetricIntervalMinute
  194. if intvMin <= 0 {
  195. intvMin = 1
  196. }
  197. diff.interval = time.Duration(intvMin) * time.Minute
  198. }
  199. for k := range nowSnap.stats {
  200. v := nowSnap.stats[k]
  201. diffStats := sHttpStatsExt{
  202. Duration2xx: v.HttpCode2xx,
  203. Duration4xx: v.HttpCode4xx,
  204. Duration5xx: v.HttpCode5xx,
  205. Hit2xx: v.HitHttpCode2xx,
  206. Hit4xx: v.HitHttpCode4xx,
  207. Hit5xx: v.HitHttpCode5xx,
  208. Method: v.Method,
  209. Path: v.Path,
  210. Name: v.Name,
  211. }
  212. if prevSnap != nil {
  213. if prevStats, ok := prevSnap.stats[k]; ok {
  214. diffStats.HasDiff = true
  215. diffStats.Duration2xxDiff = v.HttpCode2xx - prevStats.HttpCode2xx
  216. diffStats.Duration4xxDiff = v.HttpCode4xx - prevStats.HttpCode4xx
  217. diffStats.Duration5xxDiff = v.HttpCode5xx - prevStats.HttpCode5xx
  218. diffStats.Hit2xxDiff = v.HitHttpCode2xx - prevStats.HitHttpCode2xx
  219. diffStats.Hit4xxDiff = v.HitHttpCode4xx - prevStats.HitHttpCode4xx
  220. diffStats.Hit5xxDiff = v.HitHttpCode5xx - prevStats.HitHttpCode5xx
  221. }
  222. }
  223. diff.stats[k] = &diffStats
  224. }
  225. return &diff
  226. }
  227. var (
  228. httpStatsSnapshot = map[string]*sHttpStatsSnapshot{}
  229. )
  230. func getStatsKey(method, path, name string) string {
  231. return fmt.Sprintf("%s.%s.%s", method, path, name)
  232. }
  233. func getSnapshotKey(serviceName, url string) string {
  234. return fmt.Sprintf("%s.%s", serviceName, url)
  235. }
  236. func updateHttpStatsSnapshot(serviceName string, url string, now time.Time, apiStats sApiHttpStats, serviceType, regionId, version string) []influxdb.SMetricData {
  237. snapshot := apiStats.convertSnapshot(now)
  238. snapshotKey := getSnapshotKey(serviceName, url)
  239. var vdiffStats *sHttpStatsDiff
  240. if prevSnap, ok := httpStatsSnapshot[snapshotKey]; ok {
  241. vdiffStats = calculateHttpStatsDiff(prevSnap, snapshot)
  242. } else {
  243. // no prev records, just add
  244. vdiffStats = calculateHttpStatsDiff(nil, snapshot)
  245. }
  246. httpStatsSnapshot[snapshotKey] = snapshot
  247. metrics := vdiffStats.metrics(serviceName, serviceType, regionId, version)
  248. log.Debugf("updateHttpStatsSnapshot %s %s snapshotAt: %s diffAt: %s intval: %f metrics: %d", serviceName, url, snapshot.snapshotAt, vdiffStats.snapshotAt, vdiffStats.interval.Seconds(), len(metrics))
  249. return metrics
  250. }
  251. func appendMetric(metrics []influxdb.SKeyValue, key string, v float64) []influxdb.SKeyValue {
  252. if v >= 0 {
  253. metrics = append(metrics, influxdb.SKeyValue{
  254. Key: key,
  255. Value: fmt.Sprintf("%f", v),
  256. })
  257. }
  258. return metrics
  259. }
  260. func (diff *sHttpStatsDiff) metrics(service, serviceType, regionId, version string) []influxdb.SMetricData {
  261. metrics := make([]influxdb.SMetricData, 0)
  262. genTags := func(v *sHttpStatsExt) []influxdb.SKeyValue {
  263. return []influxdb.SKeyValue{
  264. {
  265. Key: "service",
  266. Value: service,
  267. },
  268. {
  269. Key: "service_type",
  270. Value: serviceType,
  271. },
  272. {
  273. Key: "region",
  274. Value: regionId,
  275. },
  276. {
  277. Key: "version",
  278. Value: version,
  279. },
  280. {
  281. Key: "method",
  282. Value: v.Method,
  283. },
  284. {
  285. Key: "path",
  286. Value: v.Path,
  287. },
  288. {
  289. Key: "name",
  290. Value: v.Name,
  291. },
  292. {
  293. Key: "interval_secs",
  294. Value: fmt.Sprintf("%f", diff.interval.Seconds()),
  295. },
  296. }
  297. }
  298. for k := range diff.stats {
  299. v := diff.stats[k]
  300. // ignore stats with no hit
  301. if v.Hit() <= 0 {
  302. continue
  303. }
  304. // ignore stats with no diff
  305. if v.HasDiff && v.HitDiff() <= 0 {
  306. continue
  307. }
  308. metric := influxdb.SMetricData{
  309. Name: METIRCY_TYPE_HTTP_REQUST,
  310. Timestamp: diff.snapshotAt,
  311. Tags: genTags(v),
  312. Metrics: []influxdb.SKeyValue{
  313. {
  314. Key: "duration_ms_any",
  315. Value: fmt.Sprintf("%f", v.DurationMs()),
  316. },
  317. {
  318. Key: "hit_any",
  319. Value: fmt.Sprintf("%d", v.Hit()),
  320. },
  321. },
  322. }
  323. if v.HasDiff {
  324. metric.Metrics = appendMetric(metric.Metrics, "dura_ms_delta_any", v.DurationMsDiff())
  325. metric.Metrics = appendMetric(metric.Metrics, "hit_delta_any", float64(v.HitDiff()))
  326. metric.Metrics = appendMetric(metric.Metrics, "delay_ms_any", v.DelayMs())
  327. metric.Metrics = appendMetric(metric.Metrics, "qps_any", v.Qps(diff.interval))
  328. }
  329. metric.Metrics = append(metric.Metrics, []influxdb.SKeyValue{
  330. {
  331. Key: "duration_ms_2xx",
  332. Value: fmt.Sprintf("%f", v.Duration2xx),
  333. },
  334. {
  335. Key: "hit_2xx",
  336. Value: fmt.Sprintf("%d", v.Hit2xx),
  337. },
  338. }...)
  339. if v.HasDiff {
  340. metric.Metrics = appendMetric(metric.Metrics, "dura_ms_delta_2xx", v.Duration2xxDiff)
  341. metric.Metrics = appendMetric(metric.Metrics, "hit_delta_2xx", float64(v.Hit2xxDiff))
  342. metric.Metrics = appendMetric(metric.Metrics, "delay_ms_2xx", v.DelayMs2xx())
  343. metric.Metrics = appendMetric(metric.Metrics, "percent_hit_2xx", v.PercentHit2xx())
  344. metric.Metrics = appendMetric(metric.Metrics, "percent_duration_2xx", v.Percent2xx())
  345. metric.Metrics = appendMetric(metric.Metrics, "qps_2xx", v.Qps2xx(diff.interval))
  346. }
  347. metric.Metrics = append(metric.Metrics, []influxdb.SKeyValue{
  348. {
  349. Key: "duration_ms_4xx",
  350. Value: fmt.Sprintf("%f", v.Duration4xx),
  351. },
  352. {
  353. Key: "hit_4xx",
  354. Value: fmt.Sprintf("%d", v.Hit4xx),
  355. },
  356. }...)
  357. if v.HasDiff {
  358. metric.Metrics = appendMetric(metric.Metrics, "dura_ms_delta_4xx", v.Duration4xxDiff)
  359. metric.Metrics = appendMetric(metric.Metrics, "hit_delta_4xx", float64(v.Hit4xxDiff))
  360. metric.Metrics = appendMetric(metric.Metrics, "delay_ms_4xx", v.DelayMs4xx())
  361. metric.Metrics = appendMetric(metric.Metrics, "percent_hit_4xx", v.PercentHit4xx())
  362. metric.Metrics = appendMetric(metric.Metrics, "percent_duration_4xx", v.Percent4xx())
  363. metric.Metrics = appendMetric(metric.Metrics, "qps_4xx", v.Qps4xx(diff.interval))
  364. }
  365. metric.Metrics = append(metric.Metrics, []influxdb.SKeyValue{
  366. {
  367. Key: "duration_ms_5xx",
  368. Value: fmt.Sprintf("%f", v.Duration5xx),
  369. },
  370. {
  371. Key: "hit_5xx",
  372. Value: fmt.Sprintf("%d", v.Hit5xx),
  373. },
  374. }...)
  375. if v.HasDiff {
  376. metric.Metrics = appendMetric(metric.Metrics, "dura_ms_delta_5xx", v.Duration5xxDiff)
  377. metric.Metrics = appendMetric(metric.Metrics, "hit_delta_5xx", float64(v.Hit5xxDiff))
  378. metric.Metrics = appendMetric(metric.Metrics, "delay_ms_5xx", v.DelayMs5xx())
  379. metric.Metrics = appendMetric(metric.Metrics, "percent_hit_5xx", v.PercentHit5xx())
  380. metric.Metrics = appendMetric(metric.Metrics, "percent_duration_5xx", v.Percent5xx())
  381. metric.Metrics = appendMetric(metric.Metrics, "qps_5xx", v.Qps5xx(diff.interval))
  382. }
  383. metrics = append(metrics, metric)
  384. }
  385. return metrics
  386. }