-
Notifications
You must be signed in to change notification settings - Fork 30
/
Copy pathclass.throttle.php
176 lines (154 loc) · 4.88 KB
/
class.throttle.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
<?php
/**
* QoS Bandwidth Throttler (part of Lotos Framework)
*
* Copyright (c) 2005-2010 Artur Graniszewski ([email protected])
* All rights reserved.
*
* @category Library
* @package Lotos
* @subpackage QoS
* @copyright Copyright (c) 2005-2010 Artur Graniszewski ([email protected])
* @license GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007
* @version $Id$
*/
/**
* The main class.
*/
class Throttle
{
/**
* Last heartbeat time in microseconds.
*
* @var int
*/
protected $lastHeartBeat = 0;
/**
* First (starting) heartbeat time in microseconds.
*
* @var int
*/
protected $firstHeartBeat = 0;
/**
* Number of bytes already sent.
*
* @var int
*/
protected $bytesSent = 0;
/**
* Total sending time in microseconds.
*
* @var int
*/
protected $sendingTime = 0;
/**
* Current transfer rate in bytes per second.
*
* @var int
*/
protected $currentLimit = 0;
/**
* Is this the last packet to send?
*
* @var bool
*/
protected $isFinishing = false;
/**
* @var ThrottleConfig
*/
protected $config;
/**
* Create new instance of throttler
*
* @param IThrottleConfig $config Configuration object or null to use system defaults
* @return Throttle
*/
public function __construct(IThrottleConfig $config = null) {
if(function_exists('apache_setenv')) {
// disable gzip HTTP compression so it would not alter the transfer rate
apache_setenv('no-gzip', '1');
}
// disable the script timeout if supported by the server
if(false === strpos(ini_get('disable_functions'), 'set_time_limit')) {
// suppress the warnings (in case of the safe_mode)
@set_time_limit(0);
}
if($config) {
$this->config = $config;
} else {
$this->config = new ThrottleConfig();
}
// set the burst rate by default as the current transfer rate
$this->currentLimit = $this->config->burstLimit;
// set the output callback
ob_start(array($this, 'onFlush'), $this->config->rateLimit);
}
/**
* Throttler destructor
*
* @return void
*/
public function __destruct() {
$this->isFinishing = true;
}
/**
* Throttling mechanism
*
* @param string $buffer Input buffer
* @return string Output buffer (the same as input)
*/
public function onFlush(& $buffer) {
// do nothing when buffer is empty (in case of implicit ob_flush() or script halt)
// and check if this is a last portion of the output, if it is - do not throttle
if($buffer === "" || $this->isFinishing) {
return $buffer;
}
// cache the buffer length for futher use
$bufferLength = strlen($buffer);
// cache current microtime to save us from executing too many system request
$now = microtime(true);
// initialize last heartbeat time if this is a first iteration of the callback
if($this->lastHeartBeat === 0) {
$this->lastHeartBeat = $this->firstHeartBeat = $now;
}
// calculate how much data have we have to send to the user, so we can set the appropriate time delay
// if the buffer is smaller than the current limit (per second) send it proportionally faster than the full
// data package
$usleep = $bufferLength / $this->currentLimit;
if($usleep > 0) {
usleep($usleep * 1000000);
$this->sendingTime += $usleep;
}
// check if the burst rate is active, and if we should switch it off (in both if cases)
if($this->currentLimit === $this->config->burstLimit && $this->config->burstLimit !== $this->config->rateLimit) {
if(isset($this->config->burstSize)) {
if($this->config->burstSize < $this->bytesSent + $bufferLength) {
$this->currentLimit = $this->config->rateLimit;
}
} else {
if($now > ($this->firstHeartBeat + $this->config->burstTimeout)) {
$this->currentLimit = $this->config->rateLimit;
}
}
}
// update system statistics
$this->bytesSent += $bufferLength;
$this->lastHeartBeat = $now;
return $buffer;
}
/**
* Returns throttle statistics.
*
* @return stdClass
*/
public function getStatistics() {
if(ob_get_level() > 0) {
ob_flush();
}
$stats = new stdClass();
$stats->bytesSent = $this->bytesSent;
$stats->sendingTime = $this->sendingTime;
$stats->averageRate = $this->sendingTime > 0 ? $this->bytesSent/$this->sendingTime : $stats->bytesSent;
return $stats;
}
}