Brak opisu

measurement.go 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. // Copyright 2014 Google Inc. All Rights Reserved.
  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 measurement export utility functions to manipulate/format performance profile sample values.
  15. package measurement
  16. import (
  17. "fmt"
  18. "strings"
  19. "time"
  20. "github.com/google/pprof/profile"
  21. )
  22. // ScaleProfiles updates the units in a set of profiles to make them
  23. // compatible. It scales the profiles to the smallest unit to preserve
  24. // data.
  25. func ScaleProfiles(profiles []*profile.Profile) error {
  26. if len(profiles) == 0 {
  27. return nil
  28. }
  29. periodTypes := make([]*profile.ValueType, 0, len(profiles))
  30. for _, p := range profiles {
  31. if p.PeriodType != nil {
  32. periodTypes = append(periodTypes, p.PeriodType)
  33. }
  34. }
  35. periodType, err := CommonValueType(periodTypes)
  36. if err != nil {
  37. return fmt.Errorf("period type: %v", err)
  38. }
  39. // Identify common sample types
  40. numSampleTypes := len(profiles[0].SampleType)
  41. for _, p := range profiles[1:] {
  42. if numSampleTypes != len(p.SampleType) {
  43. return fmt.Errorf("inconsistent samples type count: %d != %d", numSampleTypes, len(p.SampleType))
  44. }
  45. }
  46. sampleType := make([]*profile.ValueType, numSampleTypes)
  47. for i := 0; i < numSampleTypes; i++ {
  48. sampleTypes := make([]*profile.ValueType, len(profiles))
  49. for j, p := range profiles {
  50. sampleTypes[j] = p.SampleType[i]
  51. }
  52. sampleType[i], err = CommonValueType(sampleTypes)
  53. if err != nil {
  54. return fmt.Errorf("sample types: %v", err)
  55. }
  56. }
  57. for _, p := range profiles {
  58. if p.PeriodType != nil && periodType != nil {
  59. period, _ := Scale(p.Period, p.PeriodType.Unit, periodType.Unit)
  60. p.Period, p.PeriodType.Unit = int64(period), periodType.Unit
  61. }
  62. ratios := make([]float64, len(p.SampleType))
  63. for i, st := range p.SampleType {
  64. if sampleType[i] == nil {
  65. ratios[i] = 1
  66. continue
  67. }
  68. ratios[i], _ = Scale(1, st.Unit, sampleType[i].Unit)
  69. p.SampleType[i].Unit = sampleType[i].Unit
  70. }
  71. if err := p.ScaleN(ratios); err != nil {
  72. return fmt.Errorf("scale: %v", err)
  73. }
  74. }
  75. return nil
  76. }
  77. // CommonValueType returns the finest type from a set of compatible
  78. // types.
  79. func CommonValueType(ts []*profile.ValueType) (*profile.ValueType, error) {
  80. if len(ts) <= 1 {
  81. return nil, nil
  82. }
  83. minType := ts[0]
  84. for _, t := range ts[1:] {
  85. if !compatibleValueTypes(minType, t) {
  86. return nil, fmt.Errorf("incompatible types: %v %v", *minType, *t)
  87. }
  88. if ratio, _ := Scale(1, t.Unit, minType.Unit); ratio < 1 {
  89. minType = t
  90. }
  91. }
  92. rcopy := *minType
  93. return &rcopy, nil
  94. }
  95. func compatibleValueTypes(v1, v2 *profile.ValueType) bool {
  96. if v1 == nil || v2 == nil {
  97. return true // No grounds to disqualify.
  98. }
  99. // Remove trailing 's' to permit minor mismatches.
  100. if t1, t2 := strings.TrimSuffix(v1.Type, "s"), strings.TrimSuffix(v2.Type, "s"); t1 != t2 {
  101. return false
  102. }
  103. return v1.Unit == v2.Unit ||
  104. (isTimeUnit(v1.Unit) && isTimeUnit(v2.Unit)) ||
  105. (isMemoryUnit(v1.Unit) && isMemoryUnit(v2.Unit))
  106. }
  107. // Scale a measurement from an unit to a different unit and returns
  108. // the scaled value and the target unit. The returned target unit
  109. // will be empty if uninteresting (could be skipped).
  110. func Scale(value int64, fromUnit, toUnit string) (float64, string) {
  111. // Avoid infinite recursion on overflow.
  112. if value < 0 && -value > 0 {
  113. v, u := Scale(-value, fromUnit, toUnit)
  114. return -v, u
  115. }
  116. if m, u, ok := memoryLabel(value, fromUnit, toUnit); ok {
  117. return m, u
  118. }
  119. if t, u, ok := timeLabel(value, fromUnit, toUnit); ok {
  120. return t, u
  121. }
  122. // Skip non-interesting units.
  123. switch toUnit {
  124. case "count", "sample", "unit", "minimum", "auto":
  125. return float64(value), ""
  126. default:
  127. return float64(value), toUnit
  128. }
  129. }
  130. // Label returns the label used to describe a certain measurement.
  131. func Label(value int64, unit string) string {
  132. return ScaledLabel(value, unit, "auto")
  133. }
  134. // ScaledLabel scales the passed-in measurement (if necessary) and
  135. // returns the label used to describe a float measurement.
  136. func ScaledLabel(value int64, fromUnit, toUnit string) string {
  137. v, u := Scale(value, fromUnit, toUnit)
  138. sv := strings.TrimSuffix(fmt.Sprintf("%.2f", v), ".00")
  139. if sv == "0" || sv == "-0" {
  140. return "0"
  141. }
  142. return sv + u
  143. }
  144. // isMemoryUnit returns whether a name is recognized as a memory size
  145. // unit.
  146. func isMemoryUnit(unit string) bool {
  147. switch strings.TrimSuffix(strings.ToLower(unit), "s") {
  148. case "byte", "b", "kilobyte", "kb", "megabyte", "mb", "gigabyte", "gb":
  149. return true
  150. }
  151. return false
  152. }
  153. func memoryLabel(value int64, fromUnit, toUnit string) (v float64, u string, ok bool) {
  154. fromUnit = strings.TrimSuffix(strings.ToLower(fromUnit), "s")
  155. toUnit = strings.TrimSuffix(strings.ToLower(toUnit), "s")
  156. switch fromUnit {
  157. case "byte", "b":
  158. case "kilobyte", "kb":
  159. value *= 1024
  160. case "megabyte", "mb":
  161. value *= 1024 * 1024
  162. case "gigabyte", "gb":
  163. value *= 1024 * 1024 * 1024
  164. default:
  165. return 0, "", false
  166. }
  167. if toUnit == "minimum" || toUnit == "auto" {
  168. switch {
  169. case value < 1024:
  170. toUnit = "b"
  171. case value < 1024*1024:
  172. toUnit = "kb"
  173. case value < 1024*1024*1024:
  174. toUnit = "mb"
  175. default:
  176. toUnit = "gb"
  177. }
  178. }
  179. var output float64
  180. switch toUnit {
  181. default:
  182. output, toUnit = float64(value), "B"
  183. case "kb", "kbyte", "kilobyte":
  184. output, toUnit = float64(value)/1024, "kB"
  185. case "mb", "mbyte", "megabyte":
  186. output, toUnit = float64(value)/(1024*1024), "MB"
  187. case "gb", "gbyte", "gigabyte":
  188. output, toUnit = float64(value)/(1024*1024*1024), "GB"
  189. }
  190. return output, toUnit, true
  191. }
  192. // isTimeUnit returns whether a name is recognized as a time unit.
  193. func isTimeUnit(unit string) bool {
  194. unit = strings.ToLower(unit)
  195. if len(unit) > 2 {
  196. unit = strings.TrimSuffix(unit, "s")
  197. }
  198. switch unit {
  199. case "nanosecond", "ns", "microsecond", "millisecond", "ms", "s", "second", "sec", "hr", "day", "week", "year":
  200. return true
  201. }
  202. return false
  203. }
  204. func timeLabel(value int64, fromUnit, toUnit string) (v float64, u string, ok bool) {
  205. fromUnit = strings.ToLower(fromUnit)
  206. if len(fromUnit) > 2 {
  207. fromUnit = strings.TrimSuffix(fromUnit, "s")
  208. }
  209. toUnit = strings.ToLower(toUnit)
  210. if len(toUnit) > 2 {
  211. toUnit = strings.TrimSuffix(toUnit, "s")
  212. }
  213. var d time.Duration
  214. switch fromUnit {
  215. case "nanosecond", "ns":
  216. d = time.Duration(value) * time.Nanosecond
  217. case "microsecond":
  218. d = time.Duration(value) * time.Microsecond
  219. case "millisecond", "ms":
  220. d = time.Duration(value) * time.Millisecond
  221. case "second", "sec", "s":
  222. d = time.Duration(value) * time.Second
  223. case "cycle":
  224. return float64(value), "", true
  225. default:
  226. return 0, "", false
  227. }
  228. if toUnit == "minimum" || toUnit == "auto" {
  229. switch {
  230. case d < 1*time.Microsecond:
  231. toUnit = "ns"
  232. case d < 1*time.Millisecond:
  233. toUnit = "us"
  234. case d < 1*time.Second:
  235. toUnit = "ms"
  236. case d < 1*time.Minute:
  237. toUnit = "sec"
  238. case d < 1*time.Hour:
  239. toUnit = "min"
  240. case d < 24*time.Hour:
  241. toUnit = "hour"
  242. case d < 15*24*time.Hour:
  243. toUnit = "day"
  244. case d < 120*24*time.Hour:
  245. toUnit = "week"
  246. default:
  247. toUnit = "year"
  248. }
  249. }
  250. var output float64
  251. dd := float64(d)
  252. switch toUnit {
  253. case "ns", "nanosecond":
  254. output, toUnit = dd/float64(time.Nanosecond), "ns"
  255. case "us", "microsecond":
  256. output, toUnit = dd/float64(time.Microsecond), "us"
  257. case "ms", "millisecond":
  258. output, toUnit = dd/float64(time.Millisecond), "ms"
  259. case "min", "minute":
  260. output, toUnit = dd/float64(time.Minute), "mins"
  261. case "hour", "hr":
  262. output, toUnit = dd/float64(time.Hour), "hrs"
  263. case "day":
  264. output, toUnit = dd/float64(24*time.Hour), "days"
  265. case "week", "wk":
  266. output, toUnit = dd/float64(7*24*time.Hour), "wks"
  267. case "year", "yr":
  268. output, toUnit = dd/float64(365*7*24*time.Hour), "yrs"
  269. default:
  270. fallthrough
  271. case "sec", "second", "s":
  272. output, toUnit = dd/float64(time.Second), "s"
  273. }
  274. return output, toUnit, true
  275. }