github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/examples/gno.land/p/demo/flow/flow.gno (about)

     1  //
     2  // Written by Maxim Khitrov (November 2012)
     3  //
     4  // XXX modified to disable blocking, time.Sleep().
     5  
     6  // Package flow provides the tools for monitoring and limiting the flow rate
     7  // of an arbitrary data stream.
     8  package flow
     9  
    10  import (
    11  	"math"
    12  	// "sync"
    13  	"time"
    14  )
    15  
    16  // Monitor monitors and limits the transfer rate of a data stream.
    17  type Monitor struct {
    18  	// mu      sync.Mutex    // Mutex guarding access to all internal fields
    19  	active  bool          // Flag indicating an active transfer
    20  	start   time.Duration // Transfer start time (clock() value)
    21  	bytes   int64         // Total number of bytes transferred
    22  	samples int64         // Total number of samples taken
    23  
    24  	rSample float64 // Most recent transfer rate sample (bytes per second)
    25  	rEMA    float64 // Exponential moving average of rSample
    26  	rPeak   float64 // Peak transfer rate (max of all rSamples)
    27  	rWindow float64 // rEMA window (seconds)
    28  
    29  	sBytes int64         // Number of bytes transferred since sLast
    30  	sLast  time.Duration // Most recent sample time (stop time when inactive)
    31  	sRate  time.Duration // Sampling rate
    32  
    33  	tBytes int64         // Number of bytes expected in the current transfer
    34  	tLast  time.Duration // Time of the most recent transfer of at least 1 byte
    35  }
    36  
    37  // New creates a new flow control monitor. Instantaneous transfer rate is
    38  // measured and updated for each sampleRate interval. windowSize determines the
    39  // weight of each sample in the exponential moving average (EMA) calculation.
    40  // The exact formulas are:
    41  //
    42  //	sampleTime = currentTime - prevSampleTime
    43  //	sampleRate = byteCount / sampleTime
    44  //	weight     = 1 - exp(-sampleTime/windowSize)
    45  //	newRate    = weight*sampleRate + (1-weight)*oldRate
    46  //
    47  // The default values for sampleRate and windowSize (if <= 0) are 100ms and 1s,
    48  // respectively.
    49  func New(sampleRate, windowSize time.Duration) *Monitor {
    50  	if sampleRate = clockRound(sampleRate); sampleRate <= 0 {
    51  		sampleRate = 5 * clockRate
    52  	}
    53  	if windowSize <= 0 {
    54  		windowSize = 1 * time.Second
    55  	}
    56  	now := clock()
    57  	return &Monitor{
    58  		active:  true,
    59  		start:   now,
    60  		rWindow: windowSize.Seconds(),
    61  		sLast:   now,
    62  		sRate:   sampleRate,
    63  		tLast:   now,
    64  	}
    65  }
    66  
    67  // Update records the transfer of n bytes and returns n. It should be called
    68  // after each Read/Write operation, even if n is 0.
    69  func (m *Monitor) Update(n int) int {
    70  	// m.mu.Lock()
    71  	m.update(n)
    72  	// m.mu.Unlock()
    73  	return n
    74  }
    75  
    76  // Hack to set the current rEMA.
    77  func (m *Monitor) SetREMA(rEMA float64) {
    78  	// m.mu.Lock()
    79  	m.rEMA = rEMA
    80  	m.samples++
    81  	// m.mu.Unlock()
    82  }
    83  
    84  // IO is a convenience method intended to wrap io.Reader and io.Writer method
    85  // execution. It calls m.Update(n) and then returns (n, err) unmodified.
    86  func (m *Monitor) IO(n int, err error) (int, error) {
    87  	return m.Update(n), err
    88  }
    89  
    90  // Done marks the transfer as finished and prevents any further updates or
    91  // limiting. Instantaneous and current transfer rates drop to 0. Update, IO, and
    92  // Limit methods become NOOPs. It returns the total number of bytes transferred.
    93  func (m *Monitor) Done() int64 {
    94  	// m.mu.Lock()
    95  	if now := m.update(0); m.sBytes > 0 {
    96  		m.reset(now)
    97  	}
    98  	m.active = false
    99  	m.tLast = 0
   100  	n := m.bytes
   101  	// m.mu.Unlock()
   102  	return n
   103  }
   104  
   105  // timeRemLimit is the maximum Status.TimeRem value.
   106  const timeRemLimit = 999*time.Hour + 59*time.Minute + 59*time.Second
   107  
   108  // Status represents the current Monitor status. All transfer rates are in bytes
   109  // per second rounded to the nearest byte.
   110  type Status struct {
   111  	Active   bool          // Flag indicating an active transfer
   112  	Start    time.Time     // Transfer start time
   113  	Duration time.Duration // Time period covered by the statistics
   114  	Idle     time.Duration // Time since the last transfer of at least 1 byte
   115  	Bytes    int64         // Total number of bytes transferred
   116  	Samples  int64         // Total number of samples taken
   117  	InstRate int64         // Instantaneous transfer rate
   118  	CurRate  int64         // Current transfer rate (EMA of InstRate)
   119  	AvgRate  int64         // Average transfer rate (Bytes / Duration)
   120  	PeakRate int64         // Maximum instantaneous transfer rate
   121  	BytesRem int64         // Number of bytes remaining in the transfer
   122  	TimeRem  time.Duration // Estimated time to completion
   123  	Progress Percent       // Overall transfer progress
   124  }
   125  
   126  func (s Status) String() string {
   127  	return "STATUS{}"
   128  }
   129  
   130  // Status returns current transfer status information. The returned value
   131  // becomes static after a call to Done.
   132  func (m *Monitor) Status() Status {
   133  	// m.mu.Lock()
   134  	now := m.update(0)
   135  	s := Status{
   136  		Active:   m.active,
   137  		Start:    clockToTime(m.start),
   138  		Duration: m.sLast - m.start,
   139  		Idle:     now - m.tLast,
   140  		Bytes:    m.bytes,
   141  		Samples:  m.samples,
   142  		PeakRate: round(m.rPeak),
   143  		BytesRem: m.tBytes - m.bytes,
   144  		Progress: percentOf(float64(m.bytes), float64(m.tBytes)),
   145  	}
   146  	if s.BytesRem < 0 {
   147  		s.BytesRem = 0
   148  	}
   149  	if s.Duration > 0 {
   150  		rAvg := float64(s.Bytes) / s.Duration.Seconds()
   151  		s.AvgRate = round(rAvg)
   152  		if s.Active {
   153  			s.InstRate = round(m.rSample)
   154  			s.CurRate = round(m.rEMA)
   155  			if s.BytesRem > 0 {
   156  				if tRate := 0.8*m.rEMA + 0.2*rAvg; tRate > 0 {
   157  					ns := float64(s.BytesRem) / tRate * 1e9
   158  					if ns > float64(timeRemLimit) {
   159  						ns = float64(timeRemLimit)
   160  					}
   161  					s.TimeRem = clockRound(time.Duration(ns))
   162  				}
   163  			}
   164  		}
   165  	}
   166  	// m.mu.Unlock()
   167  	return s
   168  }
   169  
   170  // Limit restricts the instantaneous (per-sample) data flow to rate bytes per
   171  // second. It returns the maximum number of bytes (0 <= n <= want) that may be
   172  // transferred immediately without exceeding the limit. If block == true, the
   173  // call blocks until n > 0. want is returned unmodified if want < 1, rate < 1,
   174  // or the transfer is inactive (after a call to Done).
   175  //
   176  // At least one byte is always allowed to be transferred in any given sampling
   177  // period. Thus, if the sampling rate is 100ms, the lowest achievable flow rate
   178  // is 10 bytes per second.
   179  //
   180  // For usage examples, see the implementation of Reader and Writer in io.go.
   181  func (m *Monitor) Limit(want int, rate int64, block bool) (n int) {
   182  	if block {
   183  		panic("blocking not yet supported")
   184  	}
   185  	if want < 1 || rate < 1 {
   186  		return want
   187  	}
   188  	// m.mu.Lock()
   189  
   190  	// Determine the maximum number of bytes that can be sent in one sample
   191  	limit := round(float64(rate) * m.sRate.Seconds())
   192  	if limit <= 0 {
   193  		limit = 1
   194  	}
   195  
   196  	_ = m.update(0)
   197  	/* XXX
   198  	// If block == true, wait until m.sBytes < limit
   199  	if now := m.update(0); block {
   200  		for m.sBytes >= limit && m.active {
   201  			now = m.waitNextSample(now)
   202  		}
   203  	}
   204  	*/
   205  
   206  	// Make limit <= want (unlimited if the transfer is no longer active)
   207  	if limit -= m.sBytes; limit > int64(want) || !m.active {
   208  		limit = int64(want)
   209  	}
   210  	// m.mu.Unlock()
   211  
   212  	if limit < 0 {
   213  		limit = 0
   214  	}
   215  	return int(limit)
   216  }
   217  
   218  // SetTransferSize specifies the total size of the data transfer, which allows
   219  // the Monitor to calculate the overall progress and time to completion.
   220  func (m *Monitor) SetTransferSize(bytes int64) {
   221  	if bytes < 0 {
   222  		bytes = 0
   223  	}
   224  	// m.mu.Lock()
   225  	m.tBytes = bytes
   226  	// m.mu.Unlock()
   227  }
   228  
   229  // update accumulates the transferred byte count for the current sample until
   230  // clock() - m.sLast >= m.sRate. The monitor status is updated once the current
   231  // sample is done.
   232  func (m *Monitor) update(n int) (now time.Duration) {
   233  	if !m.active {
   234  		return
   235  	}
   236  	if now = clock(); n > 0 {
   237  		m.tLast = now
   238  	}
   239  	m.sBytes += int64(n)
   240  	if sTime := now - m.sLast; sTime >= m.sRate {
   241  		t := sTime.Seconds()
   242  		if m.rSample = float64(m.sBytes) / t; m.rSample > m.rPeak {
   243  			m.rPeak = m.rSample
   244  		}
   245  
   246  		// Exponential moving average using a method similar to *nix load
   247  		// average calculation. Longer sampling periods carry greater weight.
   248  		if m.samples > 0 {
   249  			w := math.Exp(-t / m.rWindow)
   250  			m.rEMA = m.rSample + w*(m.rEMA-m.rSample)
   251  		} else {
   252  			m.rEMA = m.rSample
   253  		}
   254  		m.reset(now)
   255  	}
   256  	return
   257  }
   258  
   259  // reset clears the current sample state in preparation for the next sample.
   260  func (m *Monitor) reset(sampleTime time.Duration) {
   261  	m.bytes += m.sBytes
   262  	m.samples++
   263  	m.sBytes = 0
   264  	m.sLast = sampleTime
   265  }
   266  
   267  /*
   268  // waitNextSample sleeps for the remainder of the current sample. The lock is
   269  // released and reacquired during the actual sleep period, so it's possible for
   270  // the transfer to be inactive when this method returns.
   271  func (m *Monitor) waitNextSample(now time.Duration) time.Duration {
   272  	const minWait = 5 * time.Millisecond
   273  	current := m.sLast
   274  
   275  	// sleep until the last sample time changes (ideally, just one iteration)
   276  	for m.sLast == current && m.active {
   277  		d := current + m.sRate - now
   278  		// m.mu.Unlock()
   279  		if d < minWait {
   280  			d = minWait
   281  		}
   282  		time.Sleep(d)
   283  		// m.mu.Lock()
   284  		now = m.update(0)
   285  	}
   286  	return now
   287  }
   288  */