timestats.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. // Copyright 2020 The go-ethereum Authors
  2. // This file is part of the go-ethereum library.
  3. //
  4. // The go-ethereum library is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // The go-ethereum library is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Lesser General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Lesser General Public License
  15. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
  16. package client
  17. import (
  18. "io"
  19. "math"
  20. "time"
  21. "github.com/ethereum/go-ethereum/les/utils"
  22. "github.com/ethereum/go-ethereum/rlp"
  23. )
  24. const (
  25. minResponseTime = time.Millisecond * 50
  26. maxResponseTime = time.Second * 10
  27. timeStatLength = 32
  28. weightScaleFactor = 1000000
  29. )
  30. // ResponseTimeStats is the response time distribution of a set of answered requests,
  31. // weighted with request value, either served by a single server or aggregated for
  32. // multiple servers.
  33. // It it a fixed length (timeStatLength) distribution vector with linear interpolation.
  34. // The X axis (the time values) are not linear, they should be transformed with
  35. // TimeToStatScale and StatScaleToTime.
  36. type (
  37. ResponseTimeStats struct {
  38. stats [timeStatLength]uint64
  39. exp uint64
  40. }
  41. ResponseTimeWeights [timeStatLength]float64
  42. )
  43. var timeStatsLogFactor = (timeStatLength - 1) / (math.Log(float64(maxResponseTime)/float64(minResponseTime)) + 1)
  44. // TimeToStatScale converts a response time to a distribution vector index. The index
  45. // is represented by a float64 so that linear interpolation can be applied.
  46. func TimeToStatScale(d time.Duration) float64 {
  47. if d < 0 {
  48. return 0
  49. }
  50. r := float64(d) / float64(minResponseTime)
  51. if r > 1 {
  52. r = math.Log(r) + 1
  53. }
  54. r *= timeStatsLogFactor
  55. if r > timeStatLength-1 {
  56. return timeStatLength - 1
  57. }
  58. return r
  59. }
  60. // StatScaleToTime converts a distribution vector index to a response time. The index
  61. // is represented by a float64 so that linear interpolation can be applied.
  62. func StatScaleToTime(r float64) time.Duration {
  63. r /= timeStatsLogFactor
  64. if r > 1 {
  65. r = math.Exp(r - 1)
  66. }
  67. return time.Duration(r * float64(minResponseTime))
  68. }
  69. // TimeoutWeights calculates the weight function used for calculating service value
  70. // based on the response time distribution of the received service.
  71. // It is based on the request timeout value of the system. It consists of a half cosine
  72. // function starting with 1, crossing zero at timeout and reaching -1 at 2*timeout.
  73. // After 2*timeout the weight is constant -1.
  74. func TimeoutWeights(timeout time.Duration) (res ResponseTimeWeights) {
  75. for i := range res {
  76. t := StatScaleToTime(float64(i))
  77. if t < 2*timeout {
  78. res[i] = math.Cos(math.Pi / 2 * float64(t) / float64(timeout))
  79. } else {
  80. res[i] = -1
  81. }
  82. }
  83. return
  84. }
  85. // EncodeRLP implements rlp.Encoder
  86. func (rt *ResponseTimeStats) EncodeRLP(w io.Writer) error {
  87. enc := struct {
  88. Stats [timeStatLength]uint64
  89. Exp uint64
  90. }{rt.stats, rt.exp}
  91. return rlp.Encode(w, &enc)
  92. }
  93. // DecodeRLP implements rlp.Decoder
  94. func (rt *ResponseTimeStats) DecodeRLP(s *rlp.Stream) error {
  95. var enc struct {
  96. Stats [timeStatLength]uint64
  97. Exp uint64
  98. }
  99. if err := s.Decode(&enc); err != nil {
  100. return err
  101. }
  102. rt.stats, rt.exp = enc.Stats, enc.Exp
  103. return nil
  104. }
  105. // Add adds a new response time with the given weight to the distribution.
  106. func (rt *ResponseTimeStats) Add(respTime time.Duration, weight float64, expFactor utils.ExpirationFactor) {
  107. rt.setExp(expFactor.Exp)
  108. weight *= expFactor.Factor * weightScaleFactor
  109. r := TimeToStatScale(respTime)
  110. i := int(r)
  111. r -= float64(i)
  112. rt.stats[i] += uint64(weight * (1 - r))
  113. if i < timeStatLength-1 {
  114. rt.stats[i+1] += uint64(weight * r)
  115. }
  116. }
  117. // setExp sets the power of 2 exponent of the structure, scaling base values (the vector
  118. // itself) up or down if necessary.
  119. func (rt *ResponseTimeStats) setExp(exp uint64) {
  120. if exp > rt.exp {
  121. shift := exp - rt.exp
  122. for i, v := range rt.stats {
  123. rt.stats[i] = v >> shift
  124. }
  125. rt.exp = exp
  126. }
  127. if exp < rt.exp {
  128. shift := rt.exp - exp
  129. for i, v := range rt.stats {
  130. rt.stats[i] = v << shift
  131. }
  132. rt.exp = exp
  133. }
  134. }
  135. // Value calculates the total service value based on the given distribution, using the
  136. // specified weight function.
  137. func (rt ResponseTimeStats) Value(weights ResponseTimeWeights, expFactor utils.ExpirationFactor) float64 {
  138. var v float64
  139. for i, s := range rt.stats {
  140. v += float64(s) * weights[i]
  141. }
  142. if v < 0 {
  143. return 0
  144. }
  145. return expFactor.Value(v, rt.exp) / weightScaleFactor
  146. }
  147. // AddStats adds the given ResponseTimeStats to the current one.
  148. func (rt *ResponseTimeStats) AddStats(s *ResponseTimeStats) {
  149. rt.setExp(s.exp)
  150. for i, v := range s.stats {
  151. rt.stats[i] += v
  152. }
  153. }
  154. // SubStats subtracts the given ResponseTimeStats from the current one.
  155. func (rt *ResponseTimeStats) SubStats(s *ResponseTimeStats) {
  156. rt.setExp(s.exp)
  157. for i, v := range s.stats {
  158. if v < rt.stats[i] {
  159. rt.stats[i] -= v
  160. } else {
  161. rt.stats[i] = 0
  162. }
  163. }
  164. }
  165. // Timeout suggests a timeout value based on the previous distribution. The parameter
  166. // is the desired rate of timeouts assuming a similar distribution in the future.
  167. // Note that the actual timeout should have a sensible minimum bound so that operating
  168. // under ideal working conditions for a long time (for example, using a local server
  169. // with very low response times) will not make it very hard for the system to accommodate
  170. // longer response times in the future.
  171. func (rt ResponseTimeStats) Timeout(failRatio float64) time.Duration {
  172. var sum uint64
  173. for _, v := range rt.stats {
  174. sum += v
  175. }
  176. s := uint64(float64(sum) * failRatio)
  177. i := timeStatLength - 1
  178. for i > 0 && s >= rt.stats[i] {
  179. s -= rt.stats[i]
  180. i--
  181. }
  182. r := float64(i) + 0.5
  183. if rt.stats[i] > 0 {
  184. r -= float64(s) / float64(rt.stats[i])
  185. }
  186. if r < 0 {
  187. r = 0
  188. }
  189. th := StatScaleToTime(r)
  190. if th > maxResponseTime {
  191. th = maxResponseTime
  192. }
  193. return th
  194. }
  195. // RtDistribution represents a distribution as a series of (X, Y) chart coordinates,
  196. // where the X axis is the response time in seconds while the Y axis is the amount of
  197. // service value received with a response time close to the X coordinate.
  198. type RtDistribution [timeStatLength][2]float64
  199. // Distribution returns a RtDistribution, optionally normalized to a sum of 1.
  200. func (rt ResponseTimeStats) Distribution(normalized bool, expFactor utils.ExpirationFactor) (res RtDistribution) {
  201. var mul float64
  202. if normalized {
  203. var sum uint64
  204. for _, v := range rt.stats {
  205. sum += v
  206. }
  207. if sum > 0 {
  208. mul = 1 / float64(sum)
  209. }
  210. } else {
  211. mul = expFactor.Value(float64(1)/weightScaleFactor, rt.exp)
  212. }
  213. for i, v := range rt.stats {
  214. res[i][0] = float64(StatScaleToTime(float64(i))) / float64(time.Second)
  215. res[i][1] = float64(v) * mul
  216. }
  217. return
  218. }