StatisticService.php 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966
  1. <?php
  2. require_once WORKERMAN_ROOT_DIR . 'Core/SocketWorker.php';
  3. require_once WORKERMAN_ROOT_DIR . 'Protocols/SimpleHttp.php';
  4. /**
  5. *
  6. * 统计中心对外服务进程 查询日志 查询接口调用量 延迟 成功率等
  7. * 采用http协议对外服务 使用http:://Server_ip:20202 地址查询统计结果
  8. *
  9. * @author walkor <worker-man@qq.com>
  10. */
  11. class StatisticService extends WORKERMAN\Core\SocketWorker
  12. {
  13. /**
  14. * 判断包是否都到达
  15. * @see Worker::dealInput()
  16. */
  17. public function dealInput($recv_str)
  18. {
  19. return \WORKERMAN\Protocols\SimpleHttp::input($recv_str);
  20. }
  21. /**
  22. * 处理业务逻辑 查询log 查询统计信息
  23. * @see Worker::dealProcess()
  24. */
  25. public function dealProcess($recv_str)
  26. {
  27. \WORKERMAN\Protocols\SimpleHttp::decode($recv_str);
  28. $module = isset($_GET['module']) ? trim($_GET['module']) : '';
  29. $interface = isset($_GET['interface']) ? trim($_GET['interface']) : '';
  30. $start_time = isset($_GET['start_time']) ? trim($_GET['start_time']) : '';
  31. $end_time = isset($_GET['end_time']) ? trim($_GET['end_time']) : '';
  32. if(0 === strpos($_SERVER['REQUEST_URI'], '/graph'))
  33. {
  34. if(!extension_loaded('gd'))
  35. {
  36. return $this->sendToClient("not suport gd\n");
  37. }
  38. $type_map = array('request','time');
  39. $type = isset($_GET['type']) && in_array($_GET['type'], $type_map) ? $_GET['type'] : 'request';
  40. $this->displayGraph($module, $interface, $type, $start_time);
  41. }
  42. // 日志
  43. elseif(0 === strpos($_SERVER['REQUEST_URI'], '/log'))
  44. {
  45. $right_str = '';
  46. $code = isset($_GET['code']) ? $_GET['code'] : '';
  47. $msg = isset($_GET['msg']) ? $_GET['msg'] : '';
  48. $pointer = isset($_GET['pointer']) ? $_GET['pointer'] : '';
  49. $count = isset($_GET['count']) ? $_GET['count'] : 100;
  50. $log_data = $this->getStasticLog($module, $interface , $start_time , $end_time, $code, $msg , $pointer, $count);
  51. if($log_data['pointer'] == 0)
  52. {
  53. return $this->display($log_data['data']);
  54. }
  55. else
  56. {
  57. $_GET['pointer'] = $log_data['pointer'];
  58. unset($_GET['end_time']);
  59. $next_page_url = http_build_query($_GET);
  60. $log_data['data'] .= "</br><center><a href='/log/?$next_page_url'>下一页</a></center>";
  61. return $this->display(nl2br($log_data['data']));
  62. }
  63. }
  64. // 统计
  65. else
  66. {
  67. // 首页
  68. if(empty($module))
  69. {
  70. return $this->home();
  71. }
  72. else
  73. {
  74. if($interface)
  75. {
  76. return $this->displayInterface($module, $interface, $start_time, $end_time);
  77. }
  78. else
  79. {
  80. return $this->display();
  81. }
  82. }
  83. }
  84. return $this->display();
  85. }
  86. /**
  87. * 统计主页
  88. * @return void
  89. */
  90. protected function home()
  91. {
  92. $data = '';
  93. $address = '127.0.0.1:10101';
  94. $sock = stream_socket_client($address);
  95. if(!$sock)
  96. {
  97. return $this->display();
  98. }
  99. fwrite($sock, 'status');
  100. $read_fds = array($sock);
  101. $write_fds = $except_fds = array();
  102. $time_start = time();
  103. while(1)
  104. {
  105. $ret = @stream_select($read_fds, $write_fds, $except_fds, 1);
  106. if(!$ret)
  107. {
  108. if(time() - $time_start >= 1)
  109. {
  110. break;
  111. }
  112. continue;
  113. }
  114. foreach($read_fds as $fd)
  115. {
  116. if($ret_str = fread($fd, 8192))
  117. {
  118. $data .= $ret_str;
  119. }
  120. else
  121. {
  122. break;
  123. }
  124. }
  125. if(time() - $time_start >= 1)
  126. {
  127. break;
  128. }
  129. }
  130. $data = '<pre>'.$data.'</pre>';
  131. return $this->display($data);
  132. }
  133. /**
  134. * 接口统计信息
  135. * @param string $module
  136. * @param string $interface
  137. * @param int $start_time
  138. * @param int $end_time
  139. * @return void
  140. */
  141. protected function displayInterface($module ,$interface, $start_time, $end_time)
  142. {
  143. $data = $this->getStatistic($module, $interface, $start_time, $end_time);
  144. $suport_gd = extension_loaded('gd');
  145. $right_str = '
  146. <center>模块:'.$module.' &nbsp; 接口:'.$interface.'</center>
  147. </br>
  148. '.($suport_gd ? '
  149. <img src="/graph/?module='.$module.'&interface='.$interface.'&type=request&start_time='.$start_time.'"/>' : '未安装gd库,图形无法展示') .'
  150. <center>请求量</center>
  151. </br>
  152. '.($suport_gd ? '
  153. <img src="/graph/?module='.$module.'&interface='.$interface.'&type=time&start_time='.$start_time.'"/>' : '未安装gd库,图形无法展示') .'
  154. <center>延迟单位:秒</center>
  155. </br>';
  156. $right_str .= '<center>';
  157. $date_array = $this->getAvailableStDate($module, $interface);
  158. $current_key = strtotime(date('Y-m-d', $start_time ? $start_time : time()));
  159. if(!isset($date_array[$current_key]))
  160. {
  161. $date_array[$current_key] = date('Y-m-d', $current_key);
  162. }
  163. unset($_GET['start_time']);
  164. $st_url = http_build_query($_GET);
  165. $date_array_chunk = array_chunk($date_array, 7, true);
  166. if($date_array_chunk)
  167. {
  168. foreach($date_array_chunk as $date_array)
  169. {
  170. foreach($date_array as $time_stamp => $date)
  171. {
  172. $right_str .= ($current_key == $time_stamp) ? ('<a href=/st/?'.$st_url.'&start_time='.$time_stamp.'><b>'.$date.'</b></a>&nbsp;&nbsp;') : ('<a href=/st/?'.$st_url.'&start_time='.$time_stamp.'>'.$date.'</a>&nbsp;&nbsp;');
  173. }
  174. $right_str .= "<br>";
  175. }
  176. }
  177. $right_str .='<br><br></center>';
  178. $right_str .='<table>
  179. <tr align="center">
  180. <th >时间</th><th>调用总数</th><th>平均耗时</th><th>成功调用总数</th><th>成功平均耗时</th><th>失败调用总数</th><th>失败平均耗时</th><th>成功率</th>
  181. </tr>
  182. ';
  183. if($data)
  184. {
  185. foreach($data as $item)
  186. {
  187. $right_str .= "<tr align='center'><td>{$item['time']}</td><td>{$item['total_count']}</td><td>{$item['total_avg_time']}</td><td>{$item['suc_count']}</td><td>{$item['suc_avg_time']}</td><td>".($item['fail_count']>0?("<a href='/log/?module=$module&interface=$interface&start_time=".strtotime($item['time'])."&end_time=".(strtotime($item['time'])+300)."'>{$item['fail_count']}</a>"):$item['fail_count'])."</td><td>{$item['fail_avg_time']}</td><td>".($item['precent']<=98?'<font style="color:red">'.$item['precent'].'%</font>' : $item['precent'].'%')."</td></tr>\n";
  188. }
  189. }
  190. $right_str .= '</table>';
  191. return $this->display($right_str);
  192. }
  193. /**
  194. * 展示曲线图
  195. * @param string $module
  196. * @param string $interface
  197. * @param string $type
  198. * @param integer $start_time
  199. * @return void
  200. */
  201. protected function displayGraph($module ,$interface, $type = 'request', $start_time = '')
  202. {
  203. $data = $this->getStatistic($module, $interface, $start_time);
  204. \WORKERMAN\Protocols\SimpleHttp::header("Content-type: image/jpeg");
  205. $gg=new buildGraph();
  206. $d2 = $d3 = array();
  207. $time_point = $start_time ? strtotime(date('Y-m-d',$start_time)) : strtotime(date('Y-m-d'));
  208. switch($type)
  209. {
  210. case 'time':
  211. for($i=0;$i<288;$i++)
  212. {
  213. $time_point +=300;
  214. $d2[$time_point] = isset($data[$time_point]['total_avg_time']) ? $data[$time_point]['total_avg_time'] : 0;
  215. $d3[$time_point] = isset($data[$time_point]['fail_avg_time']) ? $data[$time_point]['fail_avg_time'] : 0;
  216. }
  217. break;
  218. default:
  219. for($i=0;$i<288;$i++)
  220. {
  221. $time_point +=300;
  222. $d2[$time_point] = isset($data[$time_point]['total_count']) ? $data[$time_point]['total_count'] : 0;
  223. $d3[$time_point] = isset($data[$time_point]['fail_count']) ? $data[$time_point]['fail_count'] : 0;
  224. }
  225. }
  226. $d2 = array_values($d2);
  227. $d3 = array_values($d3);
  228. $gg->addData($d2);
  229. $gg->addData($d3);
  230. $gg->setColors("088A08,b40404");
  231. ob_start();
  232. // 生成曲线图
  233. $gg->build("line",0); // 参数0表示显示所有曲线,1为显示第一条,依次类推
  234. return $this->sendToClient(\WORKERMAN\Protocols\SimpleHttp::encode(ob_get_clean()));
  235. }
  236. /**
  237. * 获取模块
  238. * @return array
  239. */
  240. public function getModules()
  241. {
  242. $st_dir = WORKERMAN_LOG_DIR . 'statistic/st/';
  243. return glob($st_dir."/*");
  244. }
  245. /**
  246. * 渲染页面
  247. * @param string $data
  248. * @return bool
  249. */
  250. protected function display($data=null)
  251. {
  252. $left_detail = '';
  253. $html_left = '<ul>';
  254. $current_module = empty($_GET['module']) ? '' : $_GET['module'];
  255. if($current_module)
  256. {
  257. $st_dir = WORKERMAN_LOG_DIR . 'statistic/st/'.$current_module.'/';
  258. $all_interface = array();
  259. foreach(glob($st_dir."*") as $file)
  260. {
  261. if(is_dir($file))
  262. {
  263. continue;
  264. }
  265. $tmp = explode("|", basename($file));
  266. $interface = trim($tmp[0]);
  267. if(isset($all_interface[$interface]))
  268. {
  269. continue;
  270. }
  271. $all_interface[$interface] = $interface;
  272. $left_detail .= '<li>&nbsp;&nbsp;&nbsp;&nbsp;<a href="/st/?module='.$current_module.'&interface='.$interface.'">'.$interface.'</a></li>';
  273. }
  274. }
  275. $modules_name_array = $this->getModules();
  276. if($modules_name_array)
  277. {
  278. foreach($modules_name_array as $module_file)
  279. {
  280. $tmp = explode("/", $module_file);
  281. $module = end($tmp);
  282. $html_left .= '<li><a href="/st/?module='.$module.'">'.$module.'</a></li>';
  283. if($module == $current_module)
  284. {
  285. $html_left .= $left_detail;
  286. }
  287. }
  288. }
  289. $display_str = <<<EOC
  290. <html>
  291. <head>
  292. <title>WORKERMAN监控</title>
  293. </head>
  294. <table>
  295. <tr valign='top'>
  296. <td style="border-right:3px solid #dddddd">$html_left</td>
  297. <td>$data</td>
  298. </tr>
  299. </table>
  300. </html>
  301. EOC;
  302. return $this->sendToClient(\WORKERMAN\Protocols\SimpleHttp::encode($display_str));
  303. }
  304. /**
  305. * 日志二分查找法
  306. * @param int $start_point
  307. * @param int $end_point
  308. * @param int $time
  309. * @param fd $fd
  310. * @return int
  311. */
  312. protected function binarySearch($start_point, $end_point, $time, $fd)
  313. {
  314. // 计算中点
  315. $mid_point = (int)(($end_point+$start_point)/2);
  316. // 定位文件指针在中点
  317. fseek($fd, $mid_point);
  318. // 读第一行
  319. $line = fgets($fd);
  320. if(feof($fd) || false === $line)
  321. {
  322. return ftell($fd);
  323. }
  324. // 第一行可能数据不全,再读一行
  325. $line = fgets($fd);
  326. if(feof($fd) || false === $line || trim($line) == '')
  327. {
  328. return ftell($fd);
  329. }
  330. // 判断是否越界
  331. $current_point = ftell($fd);
  332. if($current_point>=$end_point)
  333. {
  334. return $end_point;
  335. }
  336. // 获得时间
  337. $tmp = explode("\t", $line);
  338. $tmp_time = strtotime($tmp[0]);
  339. // 判断时间,返回指针位置
  340. if($tmp_time > $time)
  341. {
  342. return $this->binarySearch($start_point, $current_point, $time, $fd);
  343. }
  344. elseif($tmp_time < $time)
  345. {
  346. return $this->binarySearch($current_point, $end_point, $time, $fd);
  347. }
  348. else
  349. {
  350. return $current_point;
  351. }
  352. }
  353. /**
  354. * 获取指定日志
  355. * @return array
  356. */
  357. protected function getStasticLog($module, $interface , $start_time = '', $end_time = '', $code = '', $msg = '', $pointer='', $count=100)
  358. {
  359. // log文件
  360. $log_file = WORKERMAN_LOG_DIR . 'statistic/log/'. ($start_time === '' ? date('Y-m-d') : date('Y-m-d', $start_time));
  361. if(!is_readable($log_file))
  362. {
  363. return array('pointer'=>0, 'data'=>$log_file . 'not exists or not readable');
  364. }
  365. // 读文件
  366. $h = fopen($log_file, 'r');
  367. // 如果有时间,则进行二分查找,加速查询
  368. if($start_time && $pointer === '' && ($file_size = filesize($log_file) > 5000))
  369. {
  370. $pointer = $this->binarySearch(0, $file_size, $start_time-1, $h);
  371. $pointer = $pointer < 1000 ? 0 : $pointer - 1000;
  372. }
  373. // 正则表达式
  374. $pattern = "/^([\d: \-]+)\t";
  375. if($module)
  376. {
  377. $pattern .= $module."::";
  378. }
  379. else
  380. {
  381. $pattern .= ".*::";
  382. }
  383. if($interface)
  384. {
  385. $pattern .= $interface."\t";
  386. }
  387. else
  388. {
  389. $pattern .= ".*\t";
  390. }
  391. if($code !== '')
  392. {
  393. $pattern .= "code:$code\t";
  394. }
  395. else
  396. {
  397. $pattern .= "code:\d+\t";
  398. }
  399. if($msg)
  400. {
  401. $pattern .= "msg:$msg";
  402. }
  403. $pattern .= '/';
  404. // 指定偏移位置
  405. if($pointer >= 0)
  406. {
  407. fseek($h, (int)$pointer);
  408. }
  409. // 查找符合条件的数据
  410. $now_count = 0;
  411. $log_buffer = '';
  412. while(1)
  413. {
  414. if(feof($h))
  415. {
  416. break;
  417. }
  418. // 读1行
  419. $line = fgets($h);
  420. if(preg_match($pattern, $line, $match))
  421. {
  422. // 判断时间是否符合要求
  423. $time = strtotime($match[1]);
  424. if($start_time)
  425. {
  426. if($time<$start_time)
  427. {
  428. continue;
  429. }
  430. }
  431. if($end_time)
  432. {
  433. if($time>$end_time)
  434. {
  435. break;
  436. }
  437. }
  438. // 收集符合条件的log
  439. $log_buffer .= $line;
  440. if(++$now_count >= $count)
  441. {
  442. break;
  443. }
  444. }
  445. }
  446. // 记录偏移位置
  447. $pointer = ftell($h);
  448. return array('pointer'=>$pointer, 'data'=>$log_buffer);
  449. }
  450. /**
  451. * 获取统计数据
  452. * @param string $module
  453. * @param string $interface
  454. * @param integer $start_time
  455. * @param integer $end_time
  456. * @return array
  457. */
  458. protected function getStatistic($module, $interface, $start_time='',$end_time='')
  459. {
  460. // 正则表达式
  461. $need_preg_match = $start_time || $end_time;
  462. $pattern = '';
  463. if($need_preg_match)
  464. {
  465. $pattern .= "/^[\d\.]+\t(\d+)\t/";
  466. }
  467. // log文件
  468. $log_file = WORKERMAN_LOG_DIR . "statistic/st/{$module}/{$interface}|". ($start_time === '' ? date('Y-m-d') : date('Y-m-d', $start_time));
  469. if(!is_readable($log_file))
  470. {
  471. return false;
  472. }
  473. // 读文件
  474. $h = fopen($log_file, 'r');
  475. // time:[suc_count:xx,suc_cost_time:xx,fail_count:xx,fail_cost_time:xx]
  476. $st_data = array();
  477. // 汇总计算
  478. while(1)
  479. {
  480. if(feof($h))
  481. {
  482. break;
  483. }
  484. // 读1行
  485. $line = fgets($h);
  486. if(empty($line))
  487. {
  488. continue;
  489. }
  490. if($need_preg_match && preg_match($pattern, $line, $match))
  491. {
  492. // 判断时间是否符合要求
  493. $time = $match[1];
  494. if($start_time)
  495. {
  496. if($time<=$start_time)
  497. {
  498. continue;
  499. }
  500. }
  501. if($end_time)
  502. {
  503. if($time>=$end_time)
  504. {
  505. continue;
  506. }
  507. }
  508. }
  509. // line = IP time suc_count suc_cost_time fail_count fail_cost_time code_json
  510. $line_data = explode("\t", $line);
  511. $time_line = $line_data[1];
  512. $suc_count = $line_data[2];
  513. $suc_cost_time = $line_data[3];
  514. $fail_count = $line_data[4];
  515. $fail_cost_time = $line_data[5];
  516. if(!isset($st_data[$time_line]))
  517. {
  518. $st_data[$time_line] = array('suc_count'=>0, 'suc_cost_time'=>0, 'fail_count'=>0, 'fail_cost_time'=>0);
  519. }
  520. $st_data[$time_line]['suc_count'] += $suc_count;
  521. $st_data[$time_line]['suc_cost_time'] += $suc_cost_time;
  522. $st_data[$time_line]['fail_count'] += $fail_count;
  523. $st_data[$time_line]['fail_cost_time'] += $fail_cost_time;
  524. }
  525. // 按照时间排序
  526. ksort($st_data);
  527. // time => [total_count:xx,suc_count:xx,suc_avg_time:xx,fail_count:xx,fail_avg_time:xx,percent:xx]
  528. $data = array();
  529. // 计算成功率 耗时
  530. foreach($st_data as $time_line=>$item)
  531. {
  532. $data[$time_line] = array(
  533. 'time' => date('Y-m-d H:i:s', $time_line),
  534. 'total_count' => $item['suc_count']+$item['fail_count'],
  535. 'total_avg_time'=> $item['suc_count']+$item['fail_count'] == 0 ? 0 : round(($item['suc_cost_time']+$item['fail_cost_time'])/($item['suc_count']+$item['fail_count']), 4),
  536. 'suc_count' => $item['suc_count'],
  537. 'suc_avg_time' => $item['suc_count'] == 0 ? $item['suc_count'] : round($item['suc_cost_time']/$item['suc_count'], 4),
  538. 'fail_count' => $item['fail_count'],
  539. 'fail_avg_time' => $item['fail_count'] == 0 ? 0 : round($item['fail_cost_time']/$item['fail_count'], 4),
  540. 'precent' => $item['suc_count']+$item['fail_count'] == 0 ? 0 : round(($item['suc_count']*100/($item['suc_count']+$item['fail_count'])), 4),
  541. );
  542. }
  543. $max_time_line = $time_line;
  544. $time_point = $start_time ? strtotime(date('Y-m-d', $start_time)) : strtotime(date('Y-m-d'))+300;
  545. for($i=0;$i<288,$time_point<=$max_time_line;$i++)
  546. {
  547. $data[$time_point] = isset($data[$time_point]) ? $data[$time_point] :
  548. array(
  549. 'time' => date('Y-m-d H:i:s', $time_point),
  550. 'total_count' => 0,
  551. 'total_avg_time'=> 0,
  552. 'suc_count' => 0,
  553. 'suc_avg_time' => 0,
  554. 'fail_count' => 0,
  555. 'fail_avg_time' => 0,
  556. 'precent' => 100,
  557. );
  558. $time_point +=300;
  559. }
  560. ksort($data);
  561. return $data;
  562. }
  563. /**
  564. * 获取能展示统计数据的日期
  565. * @param string $module
  566. * @param string $interface
  567. * @return array
  568. */
  569. protected function getAvailableStDate($module, $interface)
  570. {
  571. $date_array = array();
  572. $st_dir = WORKERMAN_LOG_DIR . 'statistic/st/'.$module.'/';
  573. foreach(glob($st_dir."$interface|*") as $stFile)
  574. {
  575. $base_name = basename($stFile);
  576. $tmp = explode('|', $base_name);
  577. $date_array[strtotime($tmp[1])] = $tmp[1];
  578. }
  579. ksort($date_array);
  580. return $date_array;
  581. }
  582. /**
  583. * 缓冲页面输出(non-PHPdoc)
  584. * @see SocketWorker::onStart()
  585. */
  586. public function onStart()
  587. {
  588. ob_start();
  589. }
  590. /**
  591. * 获取缓冲(non-PHPdoc)
  592. * @see SocketWorker::onAlarm()
  593. */
  594. public function onAlarm()
  595. {
  596. $ob_content = ob_get_contents();
  597. if($ob_content)
  598. {
  599. \WORKERMAN\Core\Lib\Log::add('StatisticService:ob_content:'.$ob_content);
  600. ob_clean();
  601. }
  602. }
  603. }
  604. /**
  605. *
  606. * 画图的一个类
  607. *
  608. */
  609. class buildGraph {
  610. protected $graphwidth=800;
  611. protected $graphheight=300;
  612. protected $width_num=0; // 宽分多少等分
  613. protected $height_num=10; // 高分多少等分,默认为10
  614. protected $height_var=0; // 高度增量(用户数据平均数)
  615. protected $width_var=0; // 宽度增量(用户数据平均数)
  616. protected $height_max=0; // 最大数据值
  617. protected $array_data=array(); // 用户待分析的数据的二维数组
  618. protected $array_error=array(); // 收集错误信息
  619. protected $colorBg=array(255,255,255); // 图形背景-白色
  620. protected $colorGrey=array(192,192,192); // 灰色画框
  621. protected $colorBlue=array(0,0,255); // 蓝色
  622. protected $colorRed=array(255,0,0); // 红色(点)
  623. protected $colorDarkBlue=array(0, 0, 255); // 深色
  624. protected $colorBlack=array(0,0,0);
  625. protected $colorLightBlue=array(200,200,255); // 浅色
  626. protected $array_color; // 曲线着色(存储十六进制数)
  627. protected $image; // 我们的图像
  628. /**
  629. * 方法:接受用户数据
  630. */
  631. function addData($array_user_data){
  632. if(!is_array($array_user_data) or empty($array_user_data)){
  633. $this->array_error['addData']="没有可供分析的数据";
  634. return false;
  635. }
  636. $i=count($this->array_data);
  637. $this->array_data[$i]=$array_user_data;
  638. }
  639. /**
  640. * 方法:定义画布宽和长
  641. */
  642. function setImg($img_width,$img_height){
  643. $this->graphwidth=$img_width;
  644. $this->graphheight=$img_height;
  645. }
  646. /**
  647. * 设定Y轴的增量等分,默认为10份
  648. */
  649. function setHeightNum($var_y){
  650. $this->height_num=$var_y;
  651. }
  652. /**
  653. * 定义各图形各部分色彩
  654. */
  655. function getRgb($color){ // 得到十进制色彩
  656. $R=($color>>16) &0xff;
  657. $G=($color>>8) &0xff;
  658. $B=($color) & 0xff;
  659. return(array($R,$G,$B));
  660. }
  661. /**
  662. * 定义背景色
  663. * @param unknown_type $c1
  664. * @param unknown_type $c2
  665. * @param unknown_type $c3
  666. */
  667. function setColorBg($c1,$c2,$c3){
  668. $this->colorBg=array($c1,$c2,$c3);
  669. }
  670. /**
  671. * 定义画框色
  672. */
  673. function setColorGrey($c1,$c2,$c3){
  674. $this->colorGrey=array($c1,$c2,$c3);
  675. }
  676. /**
  677. * 定义蓝色
  678. * @param unknown_type $c1
  679. * @param unknown_type $c2
  680. * @param unknown_type $c3
  681. */
  682. function setColorBlue($c1,$c2,$c3){
  683. $this->colorBlue=array($c1,$c2,$c3);
  684. }
  685. /**
  686. * 定义色Red
  687. */
  688. function setColorRed($c1,$c2,$c3){
  689. $this->colorRed=array($c1,$c2,$c3);
  690. }
  691. /**
  692. * 定义深色
  693. * @param unknown_type $c1
  694. * @param unknown_type $c2
  695. * @param unknown_type $c3
  696. */
  697. function setColorDarkBlue($c1,$c2,$c3){
  698. $this->colorDarkBlue=array($c1,$c2,$c3);
  699. }
  700. /**
  701. * 定义浅色
  702. * @param unknown_type $c1
  703. * @param unknown_type $c2
  704. * @param unknown_type $c3
  705. */
  706. function setColorLightBlue($c1,$c2,$c3){
  707. $this->colorLightBlue=array($c1,$c2,$c3);
  708. }
  709. /**
  710. * 方法:由用户数据将画布分成若干等份宽
  711. * 并计算出每份多少像素
  712. */
  713. function getWidthNum(){
  714. $this->width_num=count($this->array_data[0]);
  715. }
  716. /**
  717. *
  718. * @return mixed
  719. */
  720. function getMaxHeight(){
  721. // 获得用户数据的最大值
  722. $tmpvar=array();
  723. foreach($this->array_data as$tmp_value){
  724. $tmpvar[]=max($tmp_value);
  725. }
  726. $this->height_max=max($tmpvar);
  727. return max($tmpvar);
  728. }
  729. /**
  730. *
  731. * @return number
  732. */
  733. function getHeightLength(){
  734. // 计算出每格的增量长度(用户数据,而不是图形的像素值)
  735. $max_var=$this->getMaxHeight();
  736. $max_var=ceil($max_var/$this->height_num);
  737. $first_num=substr($max_var,0,1);
  738. if(substr($max_var,1,1)){
  739. if(substr($max_var,1,1)>=5)
  740. $first_num+=1;
  741. }
  742. for($i=1;$i<strlen($max_var);$i++){
  743. $first_num.="0";
  744. }
  745. return (int)$first_num;
  746. }
  747. /**
  748. *
  749. */
  750. function getVarWh(){ // 得到高和宽的增量
  751. $this->getWidthNum();
  752. // 得到高度增量和宽度增量
  753. $this->height_var=$this->getHeightLength();
  754. $this->width_var=$this->graphwidth/$this->width_num;
  755. }
  756. /**
  757. *
  758. * @param unknown_type $str_colors
  759. */
  760. function setColors($str_colors){
  761. // 用于多条曲线的不同着色,如$str_colors="ee00ff,dd0000,cccccc"
  762. $this->array_color=explode(",",$str_colors);
  763. }
  764. /**
  765. *
  766. * @param unknown_type $var_num
  767. */
  768. function buildLine($var_num){
  769. if(!empty($var_num)){ // 如果用户只选择显示一条曲线
  770. $array_tmp[0]=$this->array_data[$var_num-1];
  771. $this->array_data=$array_tmp;
  772. }
  773. for($j=0;$j<count($this->array_data);$j++){
  774. list($R,$G,$B)=$this->getRgb(hexdec($this->array_color[$j]));
  775. $colorBlue=imagecolorallocate($this->image,$R,$G,$B);
  776. for($i=0;$i<$this->width_num-1;$i++){
  777. $height_pix=$this->height_max == 0 ? 0 : round(($this->array_data[$j][$i]/$this->height_max)*$this->graphheight);
  778. $height_next_pix= $this->height_max*$this->graphheight == 0 ? 0 : round($this->array_data[$j][$i+1]/$this->height_max*$this->graphheight);
  779. imageline($this->image,$this->width_var*$i,$this->graphheight-$height_pix,$this->width_var*($i+1),$this->graphheight-$height_next_pix,$colorBlue);
  780. }
  781. }
  782. }
  783. /**
  784. *
  785. * @param unknown_type $select_gra
  786. */
  787. function buildRectangle($select_gra){
  788. if(!empty($select_gra)){ // 用户选择显示一个矩形
  789. $select_gra-=1;
  790. }
  791. // 画矩形
  792. // 配色
  793. $colorDarkBlue=imagecolorallocate($this->image,$this->colorDarkBlue[0], $this->colorDarkBlue[1],$this->colorDarkBlue[2]);
  794. $colorLightBlue=imagecolorallocate($this->image,$this->colorLightBlue[0], $this->colorLightBlue[1],$this->colorLightBlue[2]);
  795. if(empty($select_gra))
  796. $select_gra=0;
  797. for($i=0; $i<$this->width_num; $i++){
  798. $height_pix = $this->height_max == 0 ? 0 : round(($this->array_data[$select_gra][$i]/$this->height_max)*$this->graphheight);
  799. imagefilledrectangle($this->image,$this->width_var*$i,$this->graphheight-$height_pix,$this->width_var*($i+1),$this->graphheight,$colorDarkBlue);
  800. imagefilledrectangle($this->image,($i*$this->width_var)+1,($this->graphheight-$height_pix)+1,$this->width_var*($i+1)-5,$this->graphheight-2,$colorLightBlue);
  801. }
  802. }
  803. /**
  804. *
  805. */
  806. function createCloths(){
  807. // 创建画布
  808. $this->image=imagecreate($this->graphwidth+20,$this->graphheight+20);
  809. }
  810. /**
  811. *
  812. */
  813. function createFrame(){
  814. // 创建画框
  815. $this->getVarWh();
  816. // 配色
  817. $colorBg=imagecolorallocate($this->image,$this->colorBg[0], $this->colorBg[1],$this->colorBg[2]);
  818. $colorGrey=imagecolorallocate($this->image,$this->colorGrey[0], $this->colorGrey[1],$this->colorGrey[2]);
  819. // 创建图像周围的框
  820. imageline($this->image, 0, 0, 0,$this->graphheight,$colorGrey);
  821. imageline($this->image, 0, 0,$this->graphwidth, 0,$colorGrey);
  822. imageline($this->image,($this->graphwidth-1),0,($this->graphwidth-1),($this->graphheight-1),$colorGrey);
  823. imageline($this->image,0,($this->graphheight-1),($this->graphwidth-1),($this->graphheight-1),$colorGrey);
  824. }
  825. /**
  826. *
  827. */
  828. function createLine(){
  829. // 创建网格。
  830. $this->getVarWh();
  831. $colorBg=imagecolorallocate($this->image,$this->colorBg[0], $this->colorBg[1],$this->colorBg[2]);
  832. $colorGrey=imagecolorallocate($this->image,$this->colorGrey[0], $this->colorGrey[1],$this->colorGrey[2]);
  833. $colorRed=imagecolorallocate($this->image,$this->colorRed[0], $this->colorRed[1],$this->colorRed[2]);
  834. $colorBlack=imagecolorallocate($this->image,$this->colorBlack[0], $this->colorBlack[1],$this->colorBlack[2]);
  835. for($j=0;$j<$this->width_num;$j++){
  836. if($j%12 == 0){
  837. // 画竖线
  838. imageline($this->image,$j*$this->width_var,0,$j*$this->width_var,$this->graphheight,$colorGrey);
  839. // 标出数字
  840. imagestring($this->image,2,$this->width_var*$j,$this->graphheight,$j/12,$colorBlack);
  841. }
  842. }
  843. for($i=1;$i<=$this->height_num;$i++){
  844. // 画横线
  845. imageline($this->image,0,$this->graphheight-($this->height_max*$this->graphheight == 0 ? 0 : ($this->height_var/$this->height_max*$this->graphheight)*$i),$this->graphwidth,$this->graphheight - ($this->height_max*$this->graphheight == 0 ? 0 : ($this->height_var/$this->height_max*$this->graphheight)*$i),$colorGrey);
  846. // 标出数字
  847. imagestring($this->image,2,0,$this->graphheight-($this->height_max*$this->graphheight == 0 ? 0 : ($this->height_var/$this->height_max*$this->graphheight)*$i),$this->height_var*$i,$colorBlack);
  848. }
  849. }
  850. /**
  851. *
  852. * @param unknown_type $graph
  853. * @param unknown_type $str_var
  854. */
  855. function build($graph,$str_var){
  856. // $graph是用户指定的图形种类,$str_var是生成哪个数据的图
  857. $this->createCloths(); // 先要有画布啊~~
  858. switch ($graph){
  859. case"line":
  860. $this->createFrame(); // 画个框先:)
  861. $this->createLine(); // 打上底格线
  862. $this->buildLine($str_var); // 画曲线
  863. break;
  864. case"rectangle":
  865. $this->createFrame(); // 画个框先:)
  866. $this->buildRectangle($str_var); // 画矩形
  867. $this->createLine(); // 打上底格线
  868. break;
  869. }
  870. // 输出图形并清除内存
  871. imagepng($this->image);
  872. imagedestroy($this->image);
  873. }
  874. }