Highly-opinionated (ex-bullshit-free) MTPROTO proxy for Telegram. If you use v1.0 or upgrade broke you proxy, please read the chapter Version 2
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

stats.go 4.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. package doppel
  2. import (
  3. "math"
  4. "math/rand/v2"
  5. "time"
  6. )
  7. const (
  8. StatsBisectTimes = 70
  9. StatsLowK = 0.01
  10. StatsHighK = 10.0
  11. StatsDefaultK = 0.6
  12. StatsDefaultLambda = 0.002
  13. )
  14. // Stats is responsible for generating values that are distributed according
  15. // to some statistical distribution.
  16. //
  17. // It follows several ideas:
  18. // 1. Based on nginx and Cloudflare behaviour, even if server is eager
  19. // to send a lot, they all start with small TLS packets that are
  20. // approximately MTU-sized. After
  21. // 2. After ~40 TLS records, server considers TCP session as somewhat solid
  22. // and reliable and ramps up to 4096.
  23. // 3. After ~20 TLS records more it jumps to the max 16384 bytes and keep
  24. // this size as long as it can
  25. // 4. If there is no any byte within a connection for a longer time period,
  26. // this counter resets.
  27. //
  28. // This is called Dynamic TLS Record Sizing
  29. // - https://blog.cloudflare.com/optimizing-tls-over-tcp-to-reduce-latency/
  30. // - https://community.f5.com/kb/technicalarticles/boosting-tls-performance-with-dynamic-record-sizing-on-big-ip/280798
  31. // - https://www.igvita.com/2013/10/24/optimizing-tls-record-size-and-buffering-latency/
  32. //
  33. // And this optimized for the very first byte, so web browsers could start to
  34. // render as early as possible, showing user some preliminary results, optimizing
  35. // for perceived latency.
  36. //
  37. // Since this is very typical for the website, we also aim for that.
  38. //
  39. // Another important idea is how delays between TLS packets are distributed.
  40. // In case of sending huge heavy content with max sized record, delays have
  41. // lognormal distribution. But a nature of a typical website shows that
  42. // it eagers to deliver as fast as it can in a few very first records and
  43. // could possibly slow down later.
  44. //
  45. // This is perfectly described by Weibull distribution:
  46. // - https://en.wikipedia.org/wiki/Weibull_distribution
  47. // - https://ieeexplore.ieee.org/document/6662948
  48. // - https://www.researchgate.net/publication/224621285_Traffic_modelling_and_cost_optimization_for_transmitting_traffic_messages_over_a_hybrid_broadcast_and_cellular_network
  49. // - https://ir.uitm.edu.my/id/eprint/105386/1/105386.pdf
  50. //
  51. // In other word, a combination of Dynamic TLS Record Sizing hints us for
  52. // Weibull distribution.
  53. type Stats struct {
  54. sizeLastRequested time.Time
  55. sizeCounter int
  56. // https://en.wikipedia.org/wiki/Shape_parameter
  57. k float64
  58. // https://en.wikipedia.org/wiki/Scale_parameter
  59. lambda float64
  60. }
  61. func (d *Stats) Delay() time.Duration {
  62. // u ∈ (0, 1], avoids ln(0)
  63. u := 1.0 - rand.Float64()
  64. // X = λ·(-ln U)^(1/k)
  65. generated := d.lambda * math.Pow(-math.Log(u), 1.0/d.k)
  66. // generated is in milliseconds
  67. return time.Duration(generated * float64(time.Millisecond))
  68. }
  69. func (d *Stats) Size() int {
  70. if time.Since(d.sizeLastRequested) > TLSRecordSizeResetAfter {
  71. d.sizeCounter = 0
  72. }
  73. d.sizeLastRequested = time.Now()
  74. d.sizeCounter++
  75. switch {
  76. case d.sizeCounter <= TLSCounterAccelAfter:
  77. return TLSRecordSizeStart
  78. case d.sizeCounter <= TLSCounterMaxAfter:
  79. return TLSRecordSizeAccel
  80. }
  81. return TLSRecordSizeMax
  82. }
  83. func NewStats(durations []time.Duration) *Stats {
  84. n := float64(len(durations))
  85. // in milliseconds
  86. durFloats := make([]float64, len(durations))
  87. for i, v := range durations {
  88. durFloats[i] = float64(v.Microseconds()) / 1000.0
  89. }
  90. // The bisection solves the standard Weibull MLE equation for shape
  91. // parameter k. There is no any good formula for doing that so we
  92. // approximate it by several bisections. The number of operations
  93. // is statically defined by a constant.
  94. sumLog := 0.0
  95. for _, v := range durFloats {
  96. sumLog += math.Log(v)
  97. }
  98. lowK := StatsLowK
  99. highK := StatsHighK
  100. for range StatsBisectTimes {
  101. midK := (lowK + highK) / 2.0
  102. sumXK := 0.0
  103. sumXKLog := 0.0
  104. for _, v := range durFloats {
  105. xk := math.Pow(v, midK)
  106. sumXK += xk
  107. sumXKLog += xk * math.Log(v)
  108. }
  109. if (1.0/midK)+(sumLog/n)-(sumXKLog/sumXK) > 0 {
  110. lowK = midK
  111. } else {
  112. highK = midK
  113. }
  114. }
  115. k := (lowK + highK) / 2
  116. sumXK := 0.0
  117. for _, v := range durFloats {
  118. sumXK += math.Pow(v, k)
  119. }
  120. // λ = (Σxᵢᵏ / n)^(1/k)
  121. lambda := math.Pow(sumXK/n, 1.0/k)
  122. return &Stats{
  123. k: k,
  124. lambda: lambda,
  125. }
  126. }