├── .gitignore
├── Bootstrap
├── Autoload.php
├── Provider.php
├── WebServer.php
└── Worker.php
├── Config
├── Cache
│ └── readme.md
├── Config.php
├── Redis.php
├── Server.php
└── Worker.php
├── Core
├── Cache
│ ├── FileCache.php
│ └── RedisCache.php
├── Cookie.php
├── RandomKey.php
├── Response.php
└── Session.php
├── Example
├── StatisticClient.php
└── client.php
├── LICENSE
├── Lib
├── Cache.php
└── functions.php
├── Modules
├── admin.php
├── logger.php
├── logout.php
├── main.php
├── setting.php
└── statistic.php
├── README.md
├── Views
├── admin.tpl.php
├── footer.tpl.php
├── header.tpl.php
├── log.tpl.php
├── login.tpl.php
├── main.tpl.php
├── setting.tpl.php
└── statistic.tpl.php
├── Web
├── css
│ ├── bootstrap-theme.css
│ ├── bootstrap-theme.min.css
│ ├── bootstrap.css
│ ├── bootstrap.min.css
│ └── style.css
├── img
│ ├── apple-touch-icon-114-precomposed.png
│ ├── apple-touch-icon-144-precomposed.png
│ ├── apple-touch-icon-57-precomposed.png
│ ├── apple-touch-icon-72-precomposed.png
│ ├── favicon.png
│ ├── glyphicons-halflings-white.png
│ └── glyphicons-halflings.png
├── index.php
└── js
│ ├── bootstrap.min.js
│ ├── highcharts.js
│ ├── html5shiv.js
│ ├── jquery.min.js
│ ├── less-1.3.3.min.js
│ └── scripts.js
├── data
└── readme.md
├── doc
├── 1.png
├── 2.png
├── 3.png
├── 4.png
└── statistics.conf
├── web.php
└── worker.php
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pid
2 | *.log
3 | *cache.php
4 | data/statistic/*
5 |
--------------------------------------------------------------------------------
/Bootstrap/Autoload.php:
--------------------------------------------------------------------------------
1 | $this->getModules($module),
60 | 'statistic' => $this->getStatistic($date, $module, $interface)
61 | )) . "\n";
62 | $serv->send($fd, $buffer);
63 | break;
64 | case 'get_log':
65 | $buffer = json_encode($this->getStasticLog($module, $interface, $start_time, $end_time, $code, $msg, $offset, $count)) . "\n";
66 | $serv->send($fd, $buffer);
67 | break;
68 | default:
69 | $serv->send($fd, 'pack err');
70 | }
71 | }
72 |
73 | /**
74 | * 获取模块
75 | *
76 | * @param string $current_module
77 | *
78 | * @return array
79 | */
80 | public function getModules($current_module = '')
81 | {
82 | $st_dir = Config::$dataPath . $this->statisticDir;
83 | $modules_name_array = array();
84 | foreach (glob($st_dir . "/*", GLOB_ONLYDIR) as $module_file) {
85 | $tmp = explode("/", $module_file);
86 | $module = end($tmp);
87 | $modules_name_array[$module] = array();
88 | if ($current_module == $module) {
89 | $st_dir = $st_dir . $current_module . '/';
90 | $all_interface = array();
91 | foreach (glob($st_dir . "*") as $file) {
92 | if (is_dir($file)) {
93 | continue;
94 | }
95 | list ($interface, $date) = explode(".", basename($file));
96 | $all_interface[$interface] = $interface;
97 | }
98 | $modules_name_array[$module] = $all_interface;
99 | }
100 | }
101 | return $modules_name_array;
102 | }
103 |
104 | /**
105 | * 获得统计数据
106 | *
107 | * @param string $module
108 | * @param string $interface
109 | * @param int $date
110 | *
111 | * @return bool/string
112 | */
113 | protected function getStatistic($date, $module, $interface)
114 | {
115 | if (empty($module) || empty($interface)) {
116 | return '';
117 | }
118 | // log文件
119 | $log_file = Config::$dataPath . $this->statisticDir . "{$module}/{$interface}.{$date}";
120 |
121 | $handle = @fopen($log_file, 'r');
122 | if (!$handle) {
123 | return '';
124 | }
125 |
126 | // 预处理统计数据,每5分钟一行
127 | // [time=>[ip=>['suc_count'=>xx, 'suc_cost_time'=>xx, 'fail_count'=>xx, 'fail_cost_time'=>xx, 'code_map'=>[code=>count, ..], ..], ..]
128 | $statistics_data = array();
129 | while (!feof($handle)) {
130 | $line = fgets($handle, 4096);
131 | if ($line) {
132 | $explode = explode("\t", $line);
133 | if (count($explode) < 7) {
134 | continue;
135 | }
136 | list ($ip, $time, $suc_count, $suc_cost_time, $fail_count, $fail_cost_time, $code_map) = $explode;
137 | $time = ceil($time / 300) * 300;
138 | if (!isset($statistics_data[$time])) {
139 | $statistics_data[$time] = array();
140 | }
141 | if (!isset($statistics_data[$time][$ip])) {
142 | $statistics_data[$time][$ip] = array(
143 | 'suc_count' => 0,
144 | 'suc_cost_time' => 0,
145 | 'fail_count' => 0,
146 | 'fail_cost_time' => 0,
147 | 'code_map' => array()
148 | );
149 | }
150 | $statistics_data[$time][$ip]['suc_count'] += $suc_count;
151 | $statistics_data[$time][$ip]['suc_cost_time'] += round($suc_cost_time, 5);
152 | $statistics_data[$time][$ip]['fail_count'] += $fail_count;
153 | $statistics_data[$time][$ip]['fail_cost_time'] += round($fail_cost_time, 5);
154 | $code_map = json_decode(trim($code_map), true);
155 | if ($code_map && is_array($code_map)) {
156 | foreach ($code_map as $code => $count) {
157 | if (!isset($statistics_data[$time][$ip]['code_map'][$code])) {
158 | $statistics_data[$time][$ip]['code_map'][$code] = 0;
159 | }
160 | $statistics_data[$time][$ip]['code_map'][$code] += $count;
161 | }
162 | }
163 | } // end if
164 | } // end while
165 |
166 | fclose($handle);
167 | ksort($statistics_data);
168 |
169 | // 整理数据
170 | $statistics_str = '';
171 | foreach ($statistics_data as $time => $items) {
172 | foreach ($items as $ip => $item) {
173 | $statistics_str .= "$ip\t$time\t{$item['suc_count']}\t{$item['suc_cost_time']}\t{$item['fail_count']}\t{$item['fail_cost_time']}\t"
174 | . json_encode($item['code_map']) . "\n";
175 | }
176 | }
177 | return $statistics_str;
178 | }
179 |
180 | /**
181 | * 获取指定日志
182 | */
183 | protected function getStasticLog($module, $interface, $start_time = '', $end_time = '', $code = '', $msg = '', $offset = '', $count = 100)
184 | {
185 | // log文件
186 | $log_file = Config::$dataPath . $this->logDir . (empty($start_time) ? date('Y-m-d') : date('Y-m-d', $start_time));
187 | if (!is_readable($log_file)) {
188 | return array(
189 | 'offset' => 0,
190 | 'data' => ''
191 | );
192 | }
193 | // 读文件
194 | $h = fopen($log_file, 'r');
195 |
196 | // 如果有时间,则进行二分查找,加速查询
197 | if ($start_time && $offset == 0 && ($file_size = filesize($log_file)) > 1024000) {
198 | $offset = $this->binarySearch(0, $file_size, $start_time - 1, $h);
199 | $offset = $offset < 100000 ? 0 : $offset - 100000;
200 | }
201 |
202 | // 正则表达式
203 | $pattern = "/^([\d: \-]+)\t\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\t";
204 |
205 | if ($module && $module != 'AllData') {
206 | $pattern .= $module . "::";
207 | } else {
208 | $pattern .= ".*::";
209 | }
210 |
211 | if ($interface && $module != 'AllData') {
212 | $pattern .= $interface . "\t";
213 | } else {
214 | $pattern .= ".*\t";
215 | }
216 |
217 | if ($code !== '') {
218 | $pattern .= "code:$code\t";
219 | } else {
220 | $pattern .= "code:\d+\t";
221 | }
222 |
223 | if ($msg) {
224 | $pattern .= "msg:$msg";
225 | }
226 |
227 | $pattern .= '/';
228 |
229 | // 指定偏移位置
230 | if ($offset > 0) {
231 | fseek($h, (int)$offset - 1);
232 | }
233 |
234 | // 查找符合条件的数据
235 | $now_count = 0;
236 | $log_buffer = '';
237 |
238 | while (1) {
239 | if (feof($h)) {
240 | break;
241 | }
242 | // 读1行
243 | $line = fgets($h);
244 | if (preg_match($pattern, $line, $match)) {
245 | // 判断时间是否符合要求
246 | $time = strtotime($match[1]);
247 | if ($start_time) {
248 | if ($time < $start_time) {
249 | continue;
250 | }
251 | }
252 | if ($end_time) {
253 | if ($time > $end_time) {
254 | break;
255 | }
256 | }
257 | // 收集符合条件的log
258 | $log_buffer .= $line;
259 | if (++$now_count >= $count) {
260 | break;
261 | }
262 | }
263 | }
264 | // 记录偏移位置
265 | $offset = ftell($h);
266 | return array(
267 | 'offset' => $offset,
268 | 'data' => $log_buffer
269 | );
270 | }
271 |
272 | /**
273 | * 日志二分查找法
274 | *
275 | * @param int $start_point
276 | * @param int $end_point
277 | * @param int $time
278 | * @param fd $fd
279 | *
280 | * @return int
281 | */
282 | protected function binarySearch($start_point, $end_point, $time, $fd)
283 | {
284 | if ($end_point - $start_point < 65535) {
285 | return $start_point;
286 | }
287 |
288 | // 计算中点
289 | $mid_point = (int)(($end_point + $start_point) / 2);
290 |
291 | // 定位文件指针在中点
292 | fseek($fd, $mid_point - 1);
293 |
294 | // 读第一行
295 | $line = fgets($fd);
296 | if (feof($fd) || false === $line) {
297 | return $start_point;
298 | }
299 |
300 | // 第一行可能数据不全,再读一行
301 | $line = fgets($fd);
302 | if (feof($fd) || false === $line || trim($line) == '') {
303 | return $start_point;
304 | }
305 |
306 | // 判断是否越界
307 | $current_point = ftell($fd);
308 | if ($current_point >= $end_point) {
309 | return $start_point;
310 | }
311 |
312 | // 获得时间
313 | $tmp = explode("\t", $line);
314 | $tmp_time = strtotime($tmp[0]);
315 |
316 | // 判断时间,返回指针位置
317 | if ($tmp_time > $time) {
318 | return $this->binarySearch($start_point, $current_point, $time, $fd);
319 | } elseif ($tmp_time < $time) {
320 | return $this->binarySearch($current_point, $end_point, $time, $fd);
321 | } else {
322 | return $current_point;
323 | }
324 | }
325 | }
326 |
--------------------------------------------------------------------------------
/Bootstrap/WebServer.php:
--------------------------------------------------------------------------------
1 | webPidPath = \Config\Worker::$webPidPath;
43 | }
44 | register_shutdown_function(array($this, 'handleFatal'));
45 | }
46 |
47 | /**
48 | * server start的时候调用
49 | *
50 | * @param unknown $serv
51 | */
52 | public function onStart($serv)
53 | {
54 | //设置主进程名称
55 | global $argv;
56 | $this->setProcessName("php {$argv[0]}: statistics_web");
57 |
58 | //保存进程master_pid文件比较好操作
59 | file_put_contents(BASEDIR . $this->webPidPath, $serv->master_pid);
60 |
61 | echo "\033[1A\n\033[K-----------------------\033[47;30m SWOOLE \033[0m-----------------------------\n\033[0m";
62 | echo 'swoole version:' . swoole_version() . " PHP version:" . PHP_VERSION . "\n";
63 | echo "------------------------\033[47;30m WORKERS \033[0m---------------------------\n";
64 | echo "\033[47;30mMasterPid\033[0m", str_pad('',
65 | self::$_maxMasterPidLength + 2 - strlen('MasterPid')), "\033[47;30mManagerPid\033[0m", str_pad('',
66 | self::$_maxManagerPidLength + 2 - strlen('ManagerPid')), "\033[47;30mWorkerId\033[0m", str_pad('',
67 | self::$_maxWorkerIdLength + 2 - strlen('WorkerId')), "\033[47;30mWorkerPid\033[0m\n";
68 | }
69 |
70 | /**
71 | * worker start时调用
72 | *
73 | * @param unknown $serv
74 | * @param int $worker_id
75 | */
76 | public function onWorkerStart($serv, $worker_id)
77 | {
78 | global $argv;
79 | $worker_num = isset($serv->setting['worker_num']) ? $serv->setting['worker_num'] : 1;
80 |
81 | if ($worker_id >= $worker_num) {
82 | $this->setProcessName("php {$argv[0]}: task");
83 | } else {
84 | $this->setProcessName("php {$argv[0]}: worker");
85 | }
86 | usleep($worker_id);
87 | echo str_pad($serv->master_pid, self::$_maxMasterPidLength + 2), str_pad($serv->manager_pid,
88 | self::$_maxManagerPidLength + 2), str_pad($serv->worker_id, self::$_maxWorkerIdLength + 2), str_pad($serv->worker_pid,
89 | self::$_maxWorkerIdLength), "\n";;
90 | define('APPLICATION_PATH', dirname(__DIR__));
91 | }
92 |
93 | /**
94 | * Set process name.
95 | *
96 | * @param $processName
97 | */
98 | protected function setProcessName($processName)
99 | {
100 | if (PHP_OS === 'Darwin') {
101 | return;
102 | }
103 | swoole_set_process_name($processName);
104 | }
105 |
106 | /**
107 | * 当request时调用
108 | *
109 | * @param \swoole\http\request $request
110 | * @param \swoole\http\response $response
111 | */
112 | public function onRequest($request, $response)
113 | {
114 | $_GET = $_POST = $_COOKIE = array();
115 | $resp = \Core\Response::getInstance($response);
116 | $resp->setResponse($response);
117 | if (isset($request->get)) {
118 | $_GET = $request->get;
119 | }
120 | if (isset($request->post)) {
121 | $_POST = $request->post;
122 | }
123 | if (isset($request->cookie)) {
124 | $_COOKIE = $request->cookie;
125 | }
126 | try {
127 | ob_start();
128 | include APPLICATION_PATH . '/Web/index.php';
129 | $result = ob_get_contents();
130 | ob_end_clean();
131 | $response->header("Content-Type", "text/html;charset=utf-8");
132 | $result = empty($result) ? 'No message' : $result;
133 | $response->end($result);
134 | unset($result);
135 | } catch (Exception $e) {
136 | var_dump($e);
137 | }
138 | }
139 |
140 | /**
141 | * 致命错误处理
142 | */
143 | public function handleFatal()
144 | {
145 | $error = error_get_last();
146 | if (isset($error['type'])) {
147 | switch ($error['type']) {
148 | case E_ERROR :
149 | $severity = 'ERROR:Fatal run-time errors. Errors that can not be recovered from. Execution of the script is halted';
150 | break;
151 | case E_PARSE :
152 | $severity = 'PARSE:Compile-time parse errors. Parse errors should only be generated by the parser';
153 | break;
154 | case E_DEPRECATED:
155 | $severity = 'DEPRECATED:Run-time notices. Enable this to receive warnings about code that will not work in future versions';
156 | break;
157 | case E_CORE_ERROR :
158 | $severity = 'CORE_ERROR :Fatal errors at PHP startup. This is like an E_ERROR in the PHP core';
159 | break;
160 | case E_COMPILE_ERROR :
161 | $severity = 'COMPILE ERROR:Fatal compile-time errors. This is like an E_ERROR generated by the Zend Scripting Engine';
162 | break;
163 | default:
164 | $severity = 'OTHER ERROR';
165 | break;
166 | }
167 | $message = $error['message'];
168 | $file = $error['file'];
169 | $line = $error['line'];
170 | $log = "$message ($file:$line)\nStack trace:\n";
171 | $trace = debug_backtrace();
172 | foreach ($trace as $i => $t) {
173 | if (!isset($t['file'])) {
174 | $t['file'] = 'unknown';
175 | }
176 | if (!isset($t['line'])) {
177 | $t['line'] = 0;
178 | }
179 | if (!isset($t['function'])) {
180 | $t['function'] = 'unknown';
181 | }
182 | $log .= "#$i {$t['file']}({$t['line']}): ";
183 | if (isset($t['object']) && is_object($t['object'])) {
184 | $log .= get_class($t['object']) . '->';
185 | }
186 | $log .= "{$t['function']}()\n";
187 | }
188 | if (isset($_SERVER['REQUEST_URI'])) {
189 | $log .= '[QUERY] ' . $_SERVER['REQUEST_URI'];
190 | }
191 | file_put_contents('data/web_error.log', $log);
192 | }
193 | }
194 |
195 | /**
196 | * 启动
197 | *
198 | * @param string $ip
199 | * @param int $port
200 | */
201 | public function run($ip = "0.0.0.0", $port = 6666)
202 | {
203 | $webServer = new \swoole_http_server($ip, $port);
204 | $webServer->set(\Config\Server::getWebServerConfig());
205 | $webServer->on('WorkerStart', array($this, 'onWorkerStart'));
206 | $webServer->on('request', array($this, 'onRequest'));
207 | $webServer->on('start', array($this, 'onStart'));
208 | $webServer->on('ManagerStart', function ($serv) {
209 | global $argv;
210 | $this->setProcessName("php {$argv[0]}: manager");
211 | });
212 | $webServer->start();
213 | }
214 | }
--------------------------------------------------------------------------------
/Bootstrap/Worker.php:
--------------------------------------------------------------------------------
1 | modid=>interface=>['code'=>[xx=>count,xx=>count],'suc_cost_time'=>xx,'fail_cost_time'=>xx, 'suc_count'=>xx, 'fail_count'=>xx]
61 | *
62 | * @var array
63 | */
64 | protected $statisticData = array();
65 |
66 | /**
67 | * 日志的buffer
68 | *
69 | * @var string
70 | */
71 | protected $logBuffer = '';
72 |
73 | /**
74 | * 存放统计数据的目录
75 | *
76 | * @var string
77 | */
78 | protected $statisticDir = 'statistic/statistic/';
79 |
80 | /**
81 | * 存放统计日志的目录
82 | *
83 | * @var string
84 | */
85 | protected $logDir = 'statistic/log/';
86 |
87 | /**
88 | * master pid path
89 | *
90 | * @var string
91 | */
92 | protected $masterPidPath = '/data/master.pid';
93 | protected $handleWorkerPort = 55656;
94 | protected $handleProviderPort = 55858;
95 | protected $udpFinderPort = 55859;
96 | /**
97 | * MasterPid命令时格式化输出
98 | * ManagerPid命令时格式化输出
99 | * WorkerId命令时格式化输出
100 | * WorkerPid命令时格式化输出
101 | *
102 | * @var int
103 | */
104 | protected static $_maxMasterPidLength = 12;
105 | protected static $_maxManagerPidLength = 12;
106 | protected static $_maxWorkerIdLength = 12;
107 | protected static $_maxWorkerPidLength = 12;
108 |
109 | public function __construct()
110 | {
111 | $initData = \Config\Worker::getInitData();
112 | foreach ($initData as $key => $val) {
113 | $this->$key = $val;
114 | }
115 |
116 | if (isset(\Config\Worker::$masterPidPath)) {
117 | $this->masterPidPath = \Config\Worker::$masterPidPath;
118 | }
119 |
120 | if (isset(\Config\Config::$ProviderPort)) {
121 | $this->handleProviderPort = \Config\Config::$ProviderPort;
122 | }
123 |
124 | if (isset(\Config\Config::$findProviderPort)) {
125 | $this->udpFinderPort = \Config\Config::$findProviderPort;
126 | }
127 | }
128 |
129 | public function run($ip = "0.0.0.0", $port = 55656, $mode = SWOOLE_PROCESS, $type = SWOOLE_SOCK_TCP)
130 | {
131 | if (empty($port)) {
132 | $port = $this->handleWorkerPort;
133 | } else {
134 | $this->handleWorkerPort = $port;
135 | }
136 | $serv = new \swoole_server($ip, $port, $mode, $type); //处理客户端发送的数据
137 | $serv->addlistener('0.0.0.0', $this->handleProviderPort, SWOOLE_SOCK_TCP); //处理统计页面请求的数据
138 | $serv->addlistener('0.0.0.0', $this->udpFinderPort, SWOOLE_SOCK_UDP); //recv udp broadcast
139 | $serv->config = \Config\Server::getServerConfig();
140 | $serv->set($serv->config);
141 | $serv->on('Start', array($this, 'onStart'));
142 | $serv->on('WorkerStart', array($this, 'onWorkerStart'));
143 | $serv->on('Connect', array($this, 'onConnect'));
144 | $serv->on('Receive', array($this, 'onReceive'));
145 | $serv->on('Packet', array($this, 'onPacket'));
146 | $serv->on('Task', array($this, 'onTask'));
147 | $serv->on('Finish', array($this, 'onFinish'));
148 | $serv->on('WorkerError', array($this, 'onWorkerError'));
149 | $serv->on('Close', array($this, 'onClose'));
150 | $serv->on('WorkerStop', array($this, 'onWorkerStop'));
151 | $serv->on('Shutdown', array($this, 'onShutdown'));
152 | $serv->on('ManagerStart', function ($serv) {
153 | global $argv;
154 | $this->setProcessName("php {$argv[0]}: manager");
155 | });
156 | $serv->start();
157 | }
158 |
159 | /**
160 | * onStart 回调函数
161 | *
162 | * @param \swoole\server $serv
163 | */
164 | public function onStart(\swoole\server $serv)
165 | {
166 | //设置主进程名称
167 | global $argv;
168 | $this->setProcessName("php {$argv[0]}: statistics_master");
169 |
170 | //保存进程master_pid文件比较好操作
171 | file_put_contents(BASEDIR . $this->masterPidPath, $serv->master_pid);
172 |
173 | echo "\033[1A\n\033[K-----------------------\033[47;30m SWOOLE \033[0m-----------------------------\n\033[0m";
174 | echo 'swoole version:' . swoole_version() . " PHP version:" . PHP_VERSION . "\n";
175 | echo "------------------------\033[47;30m WORKERS \033[0m---------------------------\n";
176 | echo "\033[47;30mMasterPid\033[0m", str_pad('',
177 | self::$_maxMasterPidLength + 2 - strlen('MasterPid')), "\033[47;30mManagerPid\033[0m", str_pad('',
178 | self::$_maxManagerPidLength + 2 - strlen('ManagerPid')), "\033[47;30mWorkerId\033[0m", str_pad('',
179 | self::$_maxWorkerIdLength + 2 - strlen('WorkerId')), "\033[47;30mWorkerPid\033[0m\n";
180 | }
181 |
182 | /**
183 | * 日志
184 | *
185 | * @param string $msg
186 | */
187 | public function log($msg)
188 | {
189 | echo "#" . $msg . PHP_EOL;
190 | }
191 |
192 | /**
193 | * 解包
194 | *
195 | * @param string $buffer
196 | *
197 | * @return mixed
198 | */
199 | public static function decode($buffer)
200 | {
201 | $length = unpack('N', $buffer)[1];
202 | $string = substr($buffer, -$length);
203 | $data = json_decode($string, true);
204 | return $data;
205 | }
206 |
207 | /**
208 | * 进程启动
209 | *
210 | * @param \swoole\server $serv
211 | * @param int $worker_id
212 | */
213 | public function onWorkerStart($serv, $worker_id)
214 | {
215 | $this->processRename($serv, $worker_id);
216 | // 初始化目录
217 | umask(0);
218 | $statistic_dir = Config::$dataPath . $this->statisticDir;
219 | if (!is_dir($statistic_dir)) {
220 | mkdir($statistic_dir, 0777, true);
221 | }
222 | $log_dir = Config::$dataPath . $this->logDir;
223 | if (!is_dir($log_dir)) {
224 | mkdir($log_dir, 0777, true);
225 | }
226 | }
227 |
228 | /**
229 | * Set process name.
230 | *
231 | * @param $processName
232 | */
233 | protected function setProcessName($processName)
234 | {
235 | if (PHP_OS === 'Darwin') {
236 | return;
237 | }
238 | swoole_set_process_name($processName);
239 | }
240 |
241 | /**
242 | * 修改进程名
243 | *
244 | * @param \Swoole\Server $serv
245 | * @param int $worker_id
246 | */
247 | public function processRename($serv, $worker_id)
248 | {
249 | global $argv;
250 | $worker_num = isset($serv->setting['worker_num']) ? $serv->setting['worker_num'] : 1;
251 | if ($worker_id >= $worker_num) {
252 | $this->setProcessName("php {$argv[0]}: task");
253 | } else {
254 | $this->setProcessName("php {$argv[0]}: worker");
255 | // 定时保存统计数据(不同进程数据隔离)
256 | $that = &$this;
257 | $serv->tick($this->write_period_length, function ($id) use ($that) {
258 | // echo 'tick one #worker_id:' . $worker_id . PHP_EOL;
259 | $that->writeStatisticsToDisk();
260 | $that->writeLogToDisk();
261 | });
262 |
263 | $datapath = Config::$dataPath;
264 | $expireTime = $this->expired_time;
265 | // 定时清理统计数据
266 | $serv->tick($this->clear_period_length, function ($id) use ($datapath, $expireTime, $that) {
267 | // echo 'tick two #worker_id:' . $worker_id . PHP_EOL;
268 | $that->clearDisk($datapath . $this->statisticDir, $expireTime);
269 | $that->clearDisk($datapath . $this->logDir, $expireTime);
270 | });
271 | }
272 | usleep($worker_id * 50000);//保证顺序输出格式
273 | echo str_pad($serv->master_pid, self::$_maxMasterPidLength + 2), str_pad($serv->manager_pid,
274 | self::$_maxManagerPidLength + 2), str_pad($serv->worker_id, self::$_maxWorkerIdLength + 2), str_pad($serv->worker_pid,
275 | self::$_maxWorkerIdLength), "\n";;
276 | }
277 |
278 | /**
279 | * 建立链接
280 | *
281 | * @param \swoole\server $serv
282 | * @param int $fd
283 | * @param int $from_id
284 | */
285 | public function onConnect(\swoole_server $serv, $fd, $from_id)
286 | {
287 | echo "Worker#{$serv->worker_pid} Client[$fd@$from_id]: Connect.\n";
288 | }
289 |
290 | /**
291 | * 接收数据
292 | *
293 | * @param \swoole\server $serv
294 | * @param int $fd
295 | * @param int $from_id
296 | * @param string $data
297 | *
298 | * @return mixed
299 | */
300 | public function onReceive(\swoole_server $serv, $fd, $from_id, $data)
301 | {
302 | $data = self::decode($data);
303 | $connInfo = $serv->connection_info($fd, $from_id, false);
304 | if ($connInfo['server_port'] == $this->handleWorkerPort) {
305 | $module = $data['module'];
306 | $interface = $data['interface'];
307 | $cost_time = $data['cost_time'];
308 | $success = $data['success'];
309 | $time = $data['time'];
310 | $code = $data['code'];
311 | $msg = str_replace("\n", "
", $data['msg']);
312 | $ip = $serv->connection_info($fd)['remote_ip'];
313 | // 模块接口统计
314 | $this->collectStatistics($module, $interface, $cost_time, $success, $ip, $code, $msg);
315 | // 全局统计
316 | $this->collectStatistics('AllData', 'Statistics', $cost_time, $success, $ip, $code, $msg);
317 | // 失败记录日志
318 | if (!$success) {
319 | $this->logBuffer .= date('Y-m-d H:i:s', $time) . "\t$ip\t$module::$interface\tcode:$code\tmsg:$msg\n";
320 | if (strlen($this->logBuffer) >= $this->max_log_buffer_size) {
321 | $this->writeLogToDisk();
322 | }
323 | }
324 | } else {
325 | if ($connInfo['server_port'] == $this->handleProviderPort) {
326 | $serv->task(array($fd, $data));
327 | } else {
328 | if ($connInfo['server_port'] == $this->udpFinderPort) {
329 | if (empty($data)) {
330 | return false;
331 | }
332 | // 无法解析的包
333 | if (empty($data['cmd']) || $data['cmd'] != 'REPORT_IP') {
334 | return false;
335 | }
336 | return $serv->send($fd, json_encode(array('result' => 'ok')));
337 | } else {
338 | echo '端口错误' . PHP_EOL;
339 | }
340 | }
341 | }
342 | }
343 |
344 | /**
345 | * 接收数据 UDP
346 | *
347 | * @param \swoole\server $serv
348 | * @param string $data
349 | * @param array $connInfo
350 | *
351 | * @return mixed
352 | */
353 | public function onPacket(\swoole_server $serv, $data, $connInfo)
354 | {
355 | $data = self::decode($data);
356 |
357 | if ($connInfo['server_port'] == $this->udpFinderPort) {
358 | if (empty($data)) {
359 | return false;
360 | }
361 | // 无法解析的包
362 | if (empty($data['cmd']) || $data['cmd'] != 'REPORT_IP') {
363 | return false;
364 | }
365 | $ret = $serv->sendto($connInfo['server_socket'], $connInfo['port'], json_encode(array('result' => 'ok')));
366 | if (!$ret) {
367 | $this->log("udp send error. code:".swoole_errno().",msg:".swoole_strerror(swoole_errno()));
368 | }
369 | } else {
370 | echo '端口错误' . PHP_EOL;
371 | }
372 | }
373 |
374 | /**
375 | * 收集统计数据
376 | *
377 | * @param string $module
378 | * @param string $interface
379 | * @param float $cost_time
380 | * @param int $success
381 | * @param string $ip
382 | * @param int $code
383 | * @param string $msg
384 | *
385 | * @return void
386 | */
387 | protected function collectStatistics($module, $interface, $cost_time, $success, $ip, $code, $msg)
388 | {
389 | // 统计相关信息
390 | if (!isset($this->statisticData[$ip])) {
391 | $this->statisticData[$ip] = array();
392 | }
393 | if (!isset($this->statisticData[$ip][$module])) {
394 | $this->statisticData[$ip][$module] = array();
395 | }
396 | if (!isset($this->statisticData[$ip][$module][$interface])) {
397 | $this->statisticData[$ip][$module][$interface] = array(
398 | 'code' => array(),
399 | 'suc_cost_time' => 0,
400 | 'fail_cost_time' => 0,
401 | 'suc_count' => 0,
402 | 'fail_count' => 0
403 | );
404 | }
405 | if (!isset($this->statisticData[$ip][$module][$interface]['code'][$code])) {
406 | $this->statisticData[$ip][$module][$interface]['code'][$code] = 0;
407 | }
408 | $this->statisticData[$ip][$module][$interface]['code'][$code]++;
409 | if ($success) {
410 | $this->statisticData[$ip][$module][$interface]['suc_cost_time'] += $cost_time;
411 | $this->statisticData[$ip][$module][$interface]['suc_count']++;
412 | } else {
413 | $this->statisticData[$ip][$module][$interface]['fail_cost_time'] += $cost_time;
414 | $this->statisticData[$ip][$module][$interface]['fail_count']++;
415 | }
416 | }
417 |
418 | /**
419 | * 将统计数据写入磁盘
420 | *
421 | * @return void
422 | */
423 | public function writeStatisticsToDisk()
424 | {
425 | $time = time();
426 | // 循环将每个ip的统计数据写入磁盘
427 | foreach ($this->statisticData as $ip => $mod_if_data) {
428 | foreach ($mod_if_data as $module => $items) {
429 | // 文件夹不存在则创建一个
430 | $file_dir = Config::$dataPath . $this->statisticDir . $module;
431 | if (!is_dir($file_dir)) {
432 | umask(0);
433 | mkdir($file_dir, 0777, true);
434 | }
435 | // 依次写入磁盘
436 | foreach ($items as $interface => $data) {
437 | file_put_contents($file_dir . "/{$interface}." . date('Y-m-d'),
438 | "$ip\t$time\t{$data['suc_count']}\t{$data['suc_cost_time']}\t{$data['fail_count']}\t{$data['fail_cost_time']}\t"
439 | . json_encode($data['code']) . "\n", FILE_APPEND | LOCK_EX);
440 | }
441 | }
442 | }
443 | // 清空统计
444 | $this->statisticData = array();
445 | }
446 |
447 | /**
448 | * 将日志数据写入磁盘
449 | *
450 | * @return void
451 | */
452 | public function writeLogToDisk()
453 | {
454 | // 没有统计数据则返回
455 | if (empty($this->logBuffer)) {
456 | return;
457 | }
458 | // 写入磁盘
459 | file_put_contents(Config::$dataPath . $this->logDir . date('Y-m-d'), $this->logBuffer, FILE_APPEND | LOCK_EX);
460 | $this->logBuffer = '';
461 | }
462 |
463 | /**
464 | * task任务
465 | *
466 | * @param \swoole\server $serv
467 | * @param int $task_id
468 | * @param int $from_id
469 | * @param int $data
470 | *
471 | * @return mixed
472 | */
473 | public function onTask(\swoole_server $serv, $task_id, $from_id, $data)
474 | {
475 | list($fd, $req) = $data;
476 | $provider = \Bootstrap\Provider::getInstance();
477 | $provider->message($serv, $fd, $from_id, $req);
478 | }
479 |
480 | /**
481 | * task执行完毕调用
482 | *
483 | * @param \swoole\server $serv
484 | * @param int $task_id
485 | * @param mixed $data
486 | */
487 | public function onFinish(\swoole_server $serv, $task_id, $data)
488 | {
489 | //保留回调函数,暂时不用
490 | }
491 |
492 | /**
493 | * worker出现问题调用
494 | *
495 | * @param \swoole\server $serv
496 | * @param int $worker_id
497 | * @param int $worker_pid
498 | * @param int $exit_code
499 | */
500 | public function onWorkerError(\swoole_server $serv, $worker_id, $worker_pid, $exit_code)
501 | {
502 | echo "worker abnormal exit. WorkerId=$worker_id|Pid=$worker_pid|ExitCode=$exit_code\n";
503 | }
504 |
505 | /**
506 | * 清除磁盘数据
507 | *
508 | * @param string $file
509 | * @param int $exp_time
510 | */
511 | public function clearDisk($file = null, $exp_time = 86400)
512 | {
513 | $time_now = time();
514 | //判断是否是文件
515 | if (is_file($file)) {
516 | $mtime = filemtime($file);
517 | if (!$mtime) {
518 | $this->notice("filemtime $file fail");
519 | return;
520 | }
521 | if ($time_now - $mtime > $exp_time) {
522 | unlink($file);
523 | }
524 | return;
525 | }
526 | //遍历该目录下的日志文件,判断是否过期,过期删除
527 | foreach (glob($file . "/*") as $file_name) {
528 | $this->clearDisk($file_name, $exp_time);
529 | }
530 | }
531 |
532 | /**
533 | * 链接断开
534 | *
535 | * @param \swoole\swoole $serv
536 | * @param int $fd
537 | * @param int $from_id
538 | */
539 | public function onClose($serv, $fd, $from_id)
540 | {
541 | $this->log("Worker#{$serv->worker_pid} Client[$fd@$from_id]: fd=$fd is closed");
542 | }
543 |
544 | /**
545 | * 关闭进程
546 | *
547 | * @param \swoole\server $serv
548 | * @param int $worker_id
549 | */
550 | public function onWorkerStop($serv, $worker_id)
551 | {
552 | echo "WorkerStop[$worker_id]|pid=" . $serv->worker_pid . ".\n";
553 | }
554 |
555 | /**
556 | * 关闭服务器
557 | *
558 | * @param \swoole\server $serv
559 | */
560 | public function onShutdown($serv)
561 | {
562 | echo "Server: onShutdown\n";
563 | }
564 |
565 | }
--------------------------------------------------------------------------------
/Config/Cache/readme.md:
--------------------------------------------------------------------------------
1 | 存配置缓存信息
--------------------------------------------------------------------------------
/Config/Config.php:
--------------------------------------------------------------------------------
1 | '127.0.0.1',
13 | 'port' => '6379',
14 | 'database' => 0
15 | );
16 | return $config;
17 | }
18 |
19 | public static function getSessionConfig() {
20 | $config = array(
21 | 'host' => '127.0.0.1',
22 | 'port' => '6379',
23 | 'database' => 3
24 | );
25 | return $config;
26 | }
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/Config/Server.php:
--------------------------------------------------------------------------------
1 | 10,
20 | // 协议
21 | 'open_length_check' => true,
22 | 'package_length_type' => 'N',
23 | 'package_length_offset' => 0,
24 | 'package_body_start' => 4,
25 | 'package_max_length' => 8192,
26 |
27 | 'task_ipc_mode' => 2,
28 | 'task_worker_num' => 2,
29 | 'task_max_request' => 500, // 防止内存泄漏
30 |
31 | 'user' => 'xmc',
32 | 'group' => 'xmc',
33 | 'log_file' => 'data/server.log',
34 | 'heartbeat_check_interval' => 60,
35 | 'heartbeat_idle_time' => 300,
36 | 'daemonize' => false // 守护进程改成true
37 | );
38 | return $config;
39 | }
40 |
41 | /**
42 | * 获取web server配置
43 | * @return multitype:number string boolean
44 | */
45 | public static function getWebServerConfig()
46 | {
47 | $config = array(
48 | 'worker_num' => 1, // worker进程数量
49 | 'max_request' => 1000, // 最大请求次数,当请求大于它时,将会自动重启该worker
50 | 'dispatch_mode' => 1,
51 | 'log_file' => 'data/web.log',
52 | 'daemonize' => false, // 守护进程设置成true
53 | );
54 | return $config;
55 | }
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/Config/Worker.php:
--------------------------------------------------------------------------------
1 | 1024000, //最大日志buffer,大于这个值就写磁盘
19 | 'write_period_length' => 50000, //多长时间写一次数据到磁盘(单位:毫秒)
20 | 'clear_period_length' => 100000, //多长时间清理一次老的磁盘数据(单位:毫秒)
21 | 'expired_time' => 31536000, //数据多长时间过期,过期删除统计数据(单位:毫秒)
22 | 'logBufferKey' => 'logBuffer', //日志的redis buffer key
23 | 'statisticDir' => 'statistic/statistic/', //存放统计数据的目录
24 | 'logDir' => 'statistic/log/', //存放统计日志的目录
25 | 'statisticDataKey' => 'statisticData', //redis统计数据 key
26 | );
27 | return $config;
28 | }
29 |
30 | static public $masterPidPath = '/data/master.pid'; //worker master pid path
31 | static public $webPidPath = '/data/web.pid'; //web master pid path
32 | }
--------------------------------------------------------------------------------
/Core/Cache/FileCache.php:
--------------------------------------------------------------------------------
1 | config = $config;
30 | }
31 |
32 | /**
33 | * 获取文件名
34 | * @param unknown $key
35 | * @return string
36 | */
37 | protected function getFileName($key)
38 | {
39 | $file = $this->config['cache_dir'] . '/' . trim(str_replace('_', '/', $key), '/');
40 | $dir = dirname($file);
41 | if (! is_dir($dir)) {
42 | mkdir($dir, 0755, true);
43 | }
44 | return $file;
45 | }
46 |
47 | /**
48 | * 设置缓存
49 | * @param unknown $key
50 | * @param unknown $value
51 | * @param number $timeout
52 | * @return number
53 | */
54 | function set($key, $value, $timeout = 0)
55 | {
56 | $file = $this->getFileName($key);
57 | $data["value"] = $value;
58 | $data["timeout"] = $timeout;
59 | $data["mktime"] = time();
60 | $res = file_put_contents($file, serialize($data));
61 | return $res;
62 | }
63 |
64 | /**
65 | * 获取缓存数据
66 | * @param unknown $key
67 | * @return boolean|mixed
68 | */
69 | function get($key)
70 | {
71 | $file = $this->getFileName($key);
72 | if (! is_file($file))
73 | return false;
74 | $data = unserialize(file_get_contents($file));
75 | if (empty($data) or ! isset($data['timeout']) or ! isset($data["value"])) {
76 | return false;
77 | }
78 | // 已过期
79 | if ($data["timeout"] != 0 and ($data["mktime"] + $data["timeout"]) < time()) {
80 | $this->delete($key);
81 | return false;
82 | }
83 | return $data['value'];
84 | }
85 |
86 | /**
87 | * 删除
88 | * @param unknown $key
89 | * @return boolean
90 | */
91 | function delete($key)
92 | {
93 | $file = $this->getFileName($key);
94 | return unlink($file);
95 | }
96 | }
--------------------------------------------------------------------------------
/Core/Cache/RedisCache.php:
--------------------------------------------------------------------------------
1 | config = $config;
25 | }
26 |
27 | /**
28 | * 获取redis实例
29 | *
30 | * @return \Redis
31 | */
32 | protected function getRedis()
33 | {
34 | if (empty($this->redis) || !$this->redis->info()) {
35 | $this->redis = new \Redis();
36 | if (empty($this->config)) {
37 | $this->config = \Config\Redis::getSessionConfig();
38 | }
39 | $res = $this->redis->connect($this->config['host'], $this->config['port']);
40 | if (isset($this->config['password'])) {
41 | $this->redis->auth($this->config['password']);
42 | }
43 | $this->redis->select($this->config['database']);
44 | if (empty($res)) {
45 | echo "connect Redis failed!\n";
46 | }
47 | }
48 | return $this->redis;
49 | }
50 |
51 | /**
52 | * 设置缓存
53 | *
54 | * @param string $key
55 | * @param mixed $value
56 | * @param int $timeout
57 | *
58 | * @return number
59 | */
60 | function set($key, $value, $timeout = 0)
61 | {
62 | $data["value"] = $value;
63 | $data["timeout"] = $timeout;
64 | $data["mktime"] = time();
65 | $res = $this->getRedis()->set($key, json_encode($data));
66 | return $res;
67 | }
68 |
69 | /**
70 | * 获取缓存数据
71 | *
72 | * @param string $key
73 | *
74 | * @return boolean|mixed
75 | */
76 | function get($key)
77 | {
78 | $result = $this->getRedis()->get($key);
79 | $data = json_decode($result, true);
80 |
81 | if (empty($data) or !isset($data['timeout']) or !isset($data["value"])) {
82 | return false;
83 | }
84 | // 已过期
85 | if ($data["timeout"] != 0 and ($data["mktime"] + $data["timeout"]) < time()) {
86 | $this->delete($key);
87 | return false;
88 | }
89 | return $data['value'];
90 | }
91 |
92 | /**
93 | * 删除
94 | *
95 | * @param string $key
96 | *
97 | * @return boolean
98 | */
99 | function delete($key)
100 | {
101 | $result = $this->getRedis()->del($key);
102 | return $result;
103 | }
104 | }
--------------------------------------------------------------------------------
/Core/Cookie.php:
--------------------------------------------------------------------------------
1 | response = $response;
33 | }
34 |
35 | public function get($key, $default = null)
36 | {
37 | if (! isset($_COOKIE[$key])) {
38 | return $default;
39 | } else {
40 | return $_COOKIE[$key];
41 | }
42 | }
43 |
44 | public function set($key, $value, $expire = 0)
45 | {
46 | if ($expire != 0) {
47 | $expire = time() + $expire;
48 | }
49 | $this->response->cookie($key, $value, $expire, self::$path, self::$domain, self::$secure, self::$httponly);
50 | }
51 |
52 | public function delete($key)
53 | {
54 | unset($_COOKIE[$key]);
55 | $this->set($key, null);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Core/RandomKey.php:
--------------------------------------------------------------------------------
1 | response;
29 | }
30 |
31 | public function setResponse($response)
32 | {
33 | if (!self::$instance) {
34 | self::$instance = new self();
35 | }
36 | $this->response = $response;
37 | return true;
38 | }
39 | }
--------------------------------------------------------------------------------
/Core/Session.php:
--------------------------------------------------------------------------------
1 | response
29 | * @return NULL
30 | */
31 | public function __construct()
32 | {
33 | if (! $this->cache) {
34 | $this->cache = new \Core\Cache\FileCache();
35 | }
36 | if (! $this->cookie) {
37 | $this->cookie = \Core\Cookie::getInstance();
38 | }
39 | }
40 |
41 | public static function getInstance($response)
42 | {
43 | if (!self::$instance) {
44 | self::$instance = new self($response);
45 | }
46 | self::$instance->cookie->setResponse($response);
47 | return self::$instance;
48 | }
49 |
50 |
51 | public function start($readonly = false)
52 | {
53 | $this->readonly = $readonly;
54 | $this->open = true;
55 | $sessid = $this->cookie->get(self::$cookie_key);
56 | if (empty($sessid)) {
57 | $sessid = \Core\RandomKey::randmd5(40);
58 | $this->cookie->set(self::$cookie_key, $sessid, self::$cache_life);
59 | }
60 | $_SESSION = $this->load($sessid);
61 | }
62 |
63 | public function load($sessId)
64 | {
65 | $this->sessID = $sessId;
66 | $data = $this->get($sessId);
67 | if ($data) {
68 | return unserialize($data);
69 | } else {
70 | return array();
71 | }
72 | }
73 |
74 | public function save()
75 | {
76 | return $this->set($this->sessID, serialize($_SESSION));
77 | }
78 |
79 | /**
80 | * 打开Session
81 | * @param String $pSavePath
82 | * @param String $pSessName
83 | * @return Bool TRUE/FALSE
84 | */
85 | public function open($save_path = '', $sess_name = '')
86 | {
87 | self::$cache_prefix = $save_path . '_' . $sess_name;
88 | return true;
89 | }
90 |
91 | /**
92 | * 关闭Session
93 | * @param NULL
94 | * @return Bool TRUE/FALSE
95 | */
96 | public function close()
97 | {
98 | return true;
99 | }
100 |
101 | /**
102 | * 读取Session
103 | * @param String $sessId
104 | * @return Bool TRUE/FALSE
105 | */
106 | public function get($sessId)
107 | {
108 | $session = $this->cache->get(self::$cache_prefix . $sessId);
109 | // 先读数据,如果没有,就初始化一个
110 | if (! empty($session)) {
111 | return $session;
112 | } else {
113 | return array();
114 | }
115 | }
116 |
117 | /**
118 | * 设置Session的值
119 | * @param String $wSessId
120 | * @param String $wData
121 | * @return Bool true/FALSE
122 | */
123 | public function set($sessId, $session = '')
124 | {
125 | $key = self::$cache_prefix . $sessId;
126 | $ret = $this->cache->set($key, $session, self::$cache_life);
127 | return $ret;
128 | }
129 |
130 | /**
131 | * 销毁Session
132 | * @param String $wSessId
133 | * @return Bool true/FALSE
134 | */
135 | public function delete($sessId = '')
136 | {
137 | $sessId = empty($sessId) ? $this->sessID : $sessId;
138 | return $this->cache->delete(self::$cache_prefix . $sessId);
139 | }
140 |
141 | /**
142 | * 内存回收
143 | * @param NULL
144 | * @return Bool true/FALSE
145 | */
146 | public function gc()
147 | {
148 | return true;
149 | }
150 |
151 | /**
152 | * 初始化Session,配置Session
153 | * @param NULL
154 | * @return Bool true/FALSE
155 | */
156 | function initSess()
157 | {
158 | // 不使用 GET/POST 变量方式
159 | ini_set('session.use_trans_sid', 0);
160 | // 设置垃圾回收最大生存时间
161 | ini_set('session.gc_maxlifetime', self::$cache_life);
162 | // 使用 COOKIE 保存 SESSION ID 的方式
163 | ini_set('session.use_cookies', 1);
164 | ini_set('session.cookie_path', '/');
165 | // 多主机共享保存 SESSION ID 的 COOKIE
166 | ini_set('session.cookie_domain', self::$sess_domain);
167 | // 将 session.save_handler 设置为 user,而不是默认的 files
168 | session_module_name('user');
169 | // 定义 SESSION 各项操作所对应的方法名
170 | session_set_save_handler(
171 | array($this,'open'),
172 | array($this,'close'),
173 | array($this,'get'),
174 | array($this,'set'),
175 | array($this,'delete'),
176 | array($this,'gc')
177 | );
178 | session_start();
179 | return true;
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/Example/StatisticClient.php:
--------------------------------------------------------------------------------
1 | [interface=>time_start, interface=>time_start .
12 | * ..], module=>[interface=>time_start ..], ... ]
13 | * @var array
14 | */
15 | protected static $timeMap = array();
16 |
17 | protected static $client;
18 |
19 | /**
20 | * 模块接口上报消耗时间记时
21 | * @param string $module
22 | * @param string $interface
23 | * @return void
24 | */
25 | public static function tick($module = '', $interface = '')
26 | {
27 | return self::$timeMap[$module][$interface] = microtime(true);
28 | }
29 |
30 | /**
31 | * 上报统计数据
32 | *
33 | * @param string $module
34 | * @param string $interface
35 | * @param bool $success
36 | * @param int $code
37 | * @param string $msg
38 | * @param string $report_address
39 | * @return boolean
40 | */
41 | public static function report($module, $interface, $success, $code, $msg, $report_address = '')
42 | {
43 | $report_address = $report_address ? $report_address : '127.0.0.1:55656';
44 | if (isset(self::$timeMap[$module][$interface]) && self::$timeMap[$module][$interface] > 0) {
45 | $time_start = self::$timeMap[$module][$interface];
46 | self::$timeMap[$module][$interface] = 0;
47 | } else
48 | if (isset(self::$timeMap['']['']) && self::$timeMap[''][''] > 0) {
49 | $time_start = self::$timeMap[''][''];
50 | self::$timeMap[''][''] = 0;
51 | } else {
52 | $time_start = microtime(true);
53 | }
54 |
55 | $cost_time = microtime(true) - $time_start;
56 | $bin_data = Protocol::encode($module, $interface, $cost_time, $success, $code, $msg);
57 | if (extension_loaded('swoole')) {
58 | if (! self::$client || ! self::$client->isConnected()) {
59 | self::$client = new swoole_client(SWOOLE_TCP , SWOOLE_SOCK_SYNC);
60 | list($ip, $port) = explode(':', $report_address);
61 | self::$client->connect($ip, $port);
62 | }
63 | self::$client->send($bin_data);
64 | self::$client->close();
65 | self::$client = null;
66 | } else {
67 | return self::sendData($report_address, $bin_data);
68 | }
69 | }
70 |
71 | /**
72 | * 发送数据给统计系统
73 | * @param string $address
74 | * @param string $buffer
75 | * @return boolean
76 | */
77 | public static function sendData($address, $buffer, $timeout = 10)
78 | {
79 | $socket = stream_socket_client('tcp://'.$address, $errno, $errmsg, $timeout);
80 | if (! $socket) {
81 | return false;
82 | }
83 | stream_set_timeout($socket, $timeout);
84 | return stream_socket_sendto($socket, $buffer) == strlen($buffer);
85 | }
86 | }
87 |
88 | /**
89 | * 协议swoole
90 | * @author xmc
91 | */
92 | class Protocol
93 | {
94 | /**
95 | * 编码
96 | * @param string $module
97 | * @param string $interface
98 | * @param float $cost_time
99 | * @param int $success
100 | * @param int $code
101 | * @param string $msg
102 | * @return string
103 | */
104 | public static function encode($module, $interface, $cost_time, $success, $code = 0, $msg = '')
105 | {
106 | $data = array(
107 | 'module' => $module,
108 | 'interface' => $interface,
109 | 'cost_time' => $cost_time,
110 | 'success' => $success,
111 | 'time' => time(),
112 | 'code' => $code,
113 | 'msg' => $msg
114 | );
115 | $string = json_encode($data);
116 | $packData = pack('N', strlen($string)).$string;
117 | // echo strlen($string).$string.PHP_EOL;//log
118 | return $packData;
119 | }
120 |
121 | /**
122 | * 解码
123 | */
124 | public static function decode($buffer)
125 | {
126 | $length = unpack('N', $buffer)[1];
127 | $string = substr($buffer, -$length);
128 | $data = json_decode($string, true);
129 | return $data;
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/Example/client.php:
--------------------------------------------------------------------------------
1 | 'xmc','password'=>'123456');
13 | return $res;
14 | }
15 |
16 | public static function addInfo()
17 | {
18 | $res = array();
19 | $res = array('name'=>'xmc','password'=>'123456');
20 | return $res;
21 | }
22 |
23 | public static function getErrCode()
24 | {
25 | $errcode = 10001;
26 | return $errcode;
27 | }
28 |
29 | public static function getErrMsg()
30 | {
31 | $errmsg = '添加用户失败';
32 | return $errmsg;
33 | }
34 | }
35 |
36 | include 'StatisticClient.php';
37 |
38 | // 统计开始
39 | StatisticClient::tick("User", 'addInfo');
40 | // 统计的产生,接口调用是否成功、错误码、错误日志
41 | $success = true; $code = 0; $msg = '';
42 | // 假如有个User::getInfo方法要监控
43 | $user_info = User::addInfo();
44 | if(!$user_info){
45 | // 标记失败
46 | $success = false;
47 | // 获取错误码,假如getErrCode()获得
48 | $code = User::getErrCode();
49 | // 获取错误日志,假如getErrMsg()获得
50 | $msg = User::getErrMsg();
51 | }
52 | // 上报结果
53 | $res = StatisticClient::report('User', 'addInfo', $success, $code, $msg);
54 |
55 | echo "done over...\n";
56 | var_dump($user_info,$res);
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 小eyes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/Lib/Cache.php:
--------------------------------------------------------------------------------
1 | req_buf, 'ip:port'=>req_buf, ...]
19 | * @return multitype:unknown string
20 | */
21 | function multiRequest($request_buffer_array)
22 | {
23 | \Statistics\Lib\Cache::$lastSuccessIpArray = array();
24 | $client_array = $sock_to_ip = $ip_list = array();
25 | foreach ($request_buffer_array as $address => $buffer) {
26 | list ($ip, $port) = explode(':', $address);
27 | $ip_list[$ip] = $ip;
28 | $client = new swoole_client(SWOOLE_TCP | SWOOLE_KEEP, SWOOLE_SOCK_SYNC);
29 | $client->connect($ip, $port);
30 | if (! $client) {
31 | continue;
32 | }
33 | $client_array[$address] = $client;
34 | $client_array[$address]->send(encode($buffer));
35 | $sock_to_address[(int) $client->sock] = $address;
36 | }
37 | $read = $client_array;
38 | $write = $except = $read_buffer = array();
39 | $time_start = microtime(true);
40 | $timeout = 0.99;
41 | // 轮询处理数据
42 | while (count($read) > 0) {
43 | foreach ($read as $client) {
44 | $address = $sock_to_address[(int) $client->sock];
45 | $buf = $client->recv();
46 | if (! $buf) {
47 | unset($client_array[$address]);
48 | continue;
49 | }
50 | if (! isset($read_buffer[$address])) {
51 | $read_buffer[$address] = $buf;
52 | } else {
53 | $read_buffer[$address] .= $buf;
54 | }
55 | // 数据接收完毕
56 | if (($len = strlen($read_buffer[$address])) && $read_buffer[$address][$len - 1] === "\n") {
57 | unset($client_array[$address]);
58 | }
59 | }
60 | // 超时了
61 | if (microtime(true) - $time_start > $timeout) {
62 | break;
63 | }
64 | $read = $client_array;
65 | }
66 |
67 | foreach ($read_buffer as $address => $buf) {
68 | list ($ip, $port) = explode(':', $address);
69 | \Statistics\Lib\Cache::$lastSuccessIpArray[$ip] = $ip;
70 | }
71 |
72 | \Statistics\Lib\Cache::$lastFailedIpArray = array_diff($ip_list, \Statistics\Lib\Cache::$lastSuccessIpArray);
73 |
74 | ksort($read_buffer);
75 |
76 | return $read_buffer;
77 | }
78 |
79 | /**
80 | * 检查是否登录
81 | */
82 | function check_auth()
83 | {
84 | // 如果配置中管理员用户名密码为空则说明不用验证
85 | if (Config\Config::$adminName == '' && Config\Config::$adminPassword == '') {
86 | return true;
87 | }
88 | // 进入验证流程
89 | $response = \Core\Response::getInstance()->response();
90 | $session = \Core\Session::getInstance($response);
91 | $session->start();
92 | if (! isset($_SESSION['admin'])) {
93 | if (! isset($_POST['admin_name']) || ! isset($_POST['admin_password'])) {
94 | include ST_ROOT . '/Views/login.tpl.php';
95 | return _exit();
96 | } else {
97 | $admin_name = $_POST['admin_name'];
98 | $admin_password = $_POST['admin_password'];
99 | if ($admin_name != Config\Config::$adminName || $admin_password != Config\Config::$adminPassword) {
100 | $msg = "用户名或者密码不正确";
101 | include ST_ROOT . '/Views/login.tpl.php';
102 | return _exit();
103 | }
104 | $_SESSION['admin'] = $admin_name;
105 | $_GET['fn'] = 'main';
106 | }
107 | }
108 | $session->save();
109 | return true;
110 | }
111 |
112 | /**
113 | * 退出
114 | * @param string $str
115 | */
116 | function _exit($str = '')
117 | {
118 | echo($str);
119 | return false;
120 | }
121 |
--------------------------------------------------------------------------------
/Modules/admin.php:
--------------------------------------------------------------------------------
1 | 'REPORT_IP'
26 | )));
27 | // 广播
28 | socket_sendto($socket, $buffer, strlen($buffer), 0, '255.255.255.255', \Config\Config::$findProviderPort);
29 | // 超时相关
30 | $time_start = microtime(true);
31 | $global_timeout = 1;
32 | $ip_list = array();
33 | $recv_timeout = array('sec' => 0, 'usec' => 8000);
34 | socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $recv_timeout);
35 |
36 | // 循环读数据
37 | while (microtime(true) - $time_start < $global_timeout) {
38 | $buf = $host = $port = '';
39 | if (@socket_recvfrom($socket, $buf, 65535, 0, $host, $port)) {
40 | $ip_list[$host] = $host;
41 | }
42 | }
43 |
44 | // 过滤掉已经保存的ip
45 | $count = 0;
46 | foreach ($ip_list as $ip) {
47 | if (! isset(\Statistics\Lib\Cache::$ServerIpList[$ip])) {
48 | $ip_list_str .= $ip . "\r\n";
49 | $count ++;
50 | }
51 | }
52 | $action = 'add_to_server_list';
53 | $notice_msg = "探测到{$count}个新数据源";
54 | break;
55 | case 'add_to_server_list':
56 | if (empty($_POST['ip_list'])) {
57 | $err_msg = "保存的ip列表为空";
58 | break;
59 | }
60 | $ip_list = explode("\n", $_POST['ip_list']);
61 | if ($ip_list) {
62 | foreach ($ip_list as $ip) {
63 | $ip = trim($ip);
64 | if (false !== ip2long($ip)) {
65 | \Statistics\Lib\Cache::$ServerIpList[$ip] = $ip;
66 | }
67 | }
68 | }
69 | $suc_msg = "添加成功";
70 | foreach (\Statistics\Lib\Cache::$ServerIpList as $ip) {
71 | $ip_list_str .= $ip . "\r\n";
72 | }
73 | saveServerIpListToCache();
74 | break;
75 | case 'save_server_list':
76 | if (empty($_POST['ip_list'])) {
77 | $err_msg = "保存的ip列表为空";
78 | break;
79 | }
80 | \Statistics\Lib\Cache::$ServerIpList = array();
81 | $ip_list = explode("\n", $_POST['ip_list']);
82 | if ($ip_list) {
83 | foreach ($ip_list as $ip) {
84 | $ip = trim($ip);
85 | if (false !== ip2long($ip)) {
86 | \Statistics\Lib\Cache::$ServerIpList[$ip] = $ip;
87 | }
88 | }
89 | }
90 | $suc_msg = "保存成功";
91 | foreach (\Statistics\Lib\Cache::$ServerIpList as $ip) {
92 | $ip_list_str .= $ip . "\r\n";
93 | }
94 | saveServerIpListToCache();
95 | break;
96 | default:
97 | foreach (\Statistics\Lib\Cache::$ServerIpList as $ip) {
98 | $ip_list_str .= $ip . "\r\n";
99 | }
100 | }
101 |
102 | include ST_ROOT . '/Views/header.tpl.php';
103 | include ST_ROOT . '/Views/admin.tpl.php';
104 | include ST_ROOT . '/Views/footer.tpl.php';
105 | }
106 |
107 | function saveServerIpListToCache()
108 | {
109 | foreach (glob(ST_ROOT . '/Config/Cache/*.iplist.cache.php') as $php_file) {
110 | unlink($php_file);
111 | }
112 | file_put_contents(ST_ROOT . '/Config/Cache/' . time() . '.iplist.cache.php', " $interfaces) {
12 | if ($mod == 'AllData') {
13 | continue;
14 | }
15 | $module_str .= '