Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

293 lines
7.5KB

  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\debug\panels;
  8. use Yii;
  9. use yii\debug\Panel;
  10. use yii\log\Logger;
  11. use yii\debug\models\search\Db;
  12. /**
  13. * Debugger panel that collects and displays database queries performed.
  14. *
  15. * @property array $profileLogs This property is read-only.
  16. * @property string $summaryName Short name of the panel, which will be use in summary. This property is
  17. * read-only.
  18. *
  19. * @author Qiang Xue <qiang.xue@gmail.com>
  20. * @since 2.0
  21. */
  22. class DbPanel extends Panel
  23. {
  24. /**
  25. * @var integer the threshold for determining whether the request has involved
  26. * critical number of DB queries. If the number of queries exceeds this number,
  27. * the execution is considered taking critical number of DB queries.
  28. */
  29. public $criticalQueryThreshold;
  30. /**
  31. * @var string the name of the database component to use for executing (explain) queries
  32. */
  33. public $db = 'db';
  34. /**
  35. * @var array the default ordering of the database queries. In the format of
  36. * [ property => sort direction ], for example: [ 'duration' => SORT_DESC ]
  37. * @since 2.0.7
  38. */
  39. public $defaultOrder = [
  40. 'seq' => SORT_ASC
  41. ];
  42. /**
  43. * @var array the default filter to apply to the database queries. In the format
  44. * of [ property => value ], for example: [ 'type' => 'SELECT' ]
  45. * @since 2.0.7
  46. */
  47. public $defaultFilter = [];
  48. /**
  49. * @var array db queries info extracted to array as models, to use with data provider.
  50. */
  51. private $_models;
  52. /**
  53. * @var array current database request timings
  54. */
  55. private $_timings;
  56. /**
  57. * @inheritdoc
  58. */
  59. public function init()
  60. {
  61. $this->actions['db-explain'] = [
  62. 'class' => 'yii\\debug\\actions\\db\\ExplainAction',
  63. 'panel' => $this,
  64. ];
  65. }
  66. /**
  67. * @inheritdoc
  68. */
  69. public function getName()
  70. {
  71. return 'Database';
  72. }
  73. /**
  74. * @return string short name of the panel, which will be use in summary.
  75. */
  76. public function getSummaryName()
  77. {
  78. return 'DB';
  79. }
  80. /**
  81. * @inheritdoc
  82. */
  83. public function getSummary()
  84. {
  85. $timings = $this->calculateTimings();
  86. $queryCount = count($timings);
  87. $queryTime = number_format($this->getTotalQueryTime($timings) * 1000) . ' ms';
  88. return Yii::$app->view->render('panels/db/summary', [
  89. 'timings' => $this->calculateTimings(),
  90. 'panel' => $this,
  91. 'queryCount' => $queryCount,
  92. 'queryTime' => $queryTime,
  93. ]);
  94. }
  95. /**
  96. * @inheritdoc
  97. */
  98. public function getDetail()
  99. {
  100. $searchModel = new Db();
  101. if (!$searchModel->load(Yii::$app->request->getQueryParams())) {
  102. $searchModel->load($this->defaultFilter, '');
  103. }
  104. $dataProvider = $searchModel->search($this->getModels());
  105. $dataProvider->getSort()->defaultOrder = $this->defaultOrder;
  106. return Yii::$app->view->render('panels/db/detail', [
  107. 'panel' => $this,
  108. 'dataProvider' => $dataProvider,
  109. 'searchModel' => $searchModel,
  110. 'hasExplain' => $this->hasExplain()
  111. ]);
  112. }
  113. /**
  114. * Calculates given request profile timings.
  115. *
  116. * @return array timings [token, category, timestamp, traces, nesting level, elapsed time]
  117. */
  118. public function calculateTimings()
  119. {
  120. if ($this->_timings === null) {
  121. $this->_timings = Yii::getLogger()->calculateTimings(isset($this->data['messages']) ? $this->data['messages'] : []);
  122. }
  123. return $this->_timings;
  124. }
  125. /**
  126. * @inheritdoc
  127. */
  128. public function save()
  129. {
  130. return ['messages' => $this->getProfileLogs()];
  131. }
  132. /**
  133. * Returns all profile logs of the current request for this panel. It includes categories such as:
  134. * 'yii\db\Command::query', 'yii\db\Command::execute'.
  135. * @return array
  136. */
  137. public function getProfileLogs()
  138. {
  139. $target = $this->module->logTarget;
  140. return $target->filterMessages($target->messages, Logger::LEVEL_PROFILE, ['yii\db\Command::query', 'yii\db\Command::execute']);
  141. }
  142. /**
  143. * Returns total query time.
  144. *
  145. * @param array $timings
  146. * @return int total time
  147. */
  148. protected function getTotalQueryTime($timings)
  149. {
  150. $queryTime = 0;
  151. foreach ($timings as $timing) {
  152. $queryTime += $timing['duration'];
  153. }
  154. return $queryTime;
  155. }
  156. /**
  157. * Returns an array of models that represents logs of the current request.
  158. * Can be used with data providers such as \yii\data\ArrayDataProvider.
  159. * @return array models
  160. */
  161. protected function getModels()
  162. {
  163. if ($this->_models === null) {
  164. $this->_models = [];
  165. $timings = $this->calculateTimings();
  166. foreach ($timings as $seq => $dbTiming) {
  167. $this->_models[] = [
  168. 'type' => $this->getQueryType($dbTiming['info']),
  169. 'query' => $dbTiming['info'],
  170. 'duration' => ($dbTiming['duration'] * 1000), // in milliseconds
  171. 'trace' => $dbTiming['trace'],
  172. 'timestamp' => ($dbTiming['timestamp'] * 1000), // in milliseconds
  173. 'seq' => $seq,
  174. ];
  175. }
  176. }
  177. return $this->_models;
  178. }
  179. /**
  180. * Returns database query type.
  181. *
  182. * @param string $timing timing procedure string
  183. * @return string query type such as select, insert, delete, etc.
  184. */
  185. protected function getQueryType($timing)
  186. {
  187. $timing = ltrim($timing);
  188. preg_match('/^([a-zA-z]*)/', $timing, $matches);
  189. return count($matches) ? mb_strtoupper($matches[0], 'utf8') : '';
  190. }
  191. /**
  192. * Check if given queries count is critical according settings.
  193. *
  194. * @param int $count queries count
  195. * @return bool
  196. */
  197. public function isQueryCountCritical($count)
  198. {
  199. return (($this->criticalQueryThreshold !== null) && ($count > $this->criticalQueryThreshold));
  200. }
  201. /**
  202. * Returns array query types
  203. *
  204. * @return array
  205. * @since 2.0.3
  206. */
  207. public function getTypes()
  208. {
  209. return array_reduce(
  210. $this->_models,
  211. function ($result, $item) {
  212. $result[$item['type']] = $item['type'];
  213. return $result;
  214. },
  215. []
  216. );
  217. }
  218. /**
  219. * @return bool Whether the DB component has support for EXPLAIN queries
  220. * @since 2.0.5
  221. */
  222. protected function hasExplain()
  223. {
  224. $db = $this->getDb();
  225. if (!($db instanceof \yii\db\Connection)) {
  226. return false;
  227. }
  228. switch ($db->getDriverName()) {
  229. case 'mysql':
  230. case 'sqlite':
  231. case 'pgsql':
  232. case 'cubrid':
  233. return true;
  234. default:
  235. return false;
  236. }
  237. }
  238. /**
  239. * Check if given query type can be explained.
  240. *
  241. * @param string $type query type
  242. * @return bool
  243. *
  244. * @since 2.0.5
  245. */
  246. public static function canBeExplained($type)
  247. {
  248. return $type !== 'SHOW';
  249. }
  250. /**
  251. * Returns a reference to the DB component associated with the panel
  252. *
  253. * @return \yii\db\Connection
  254. * @since 2.0.5
  255. */
  256. public function getDb()
  257. {
  258. return Yii::$app->get($this->db);
  259. }
  260. }