You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

273 lines
6.9KB

  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 db queries info extracted to array as models, to use with data provider.
  36. */
  37. private $_models;
  38. /**
  39. * @var array current database request timings
  40. */
  41. private $_timings;
  42. /**
  43. * @inheritdoc
  44. */
  45. public function init()
  46. {
  47. $this->actions['db-explain'] = [
  48. 'class' => 'yii\\debug\\actions\\db\\ExplainAction',
  49. 'panel' => $this,
  50. ];
  51. }
  52. /**
  53. * @inheritdoc
  54. */
  55. public function getName()
  56. {
  57. return 'Database';
  58. }
  59. /**
  60. * @return string short name of the panel, which will be use in summary.
  61. */
  62. public function getSummaryName()
  63. {
  64. return 'DB';
  65. }
  66. /**
  67. * @inheritdoc
  68. */
  69. public function getSummary()
  70. {
  71. $timings = $this->calculateTimings();
  72. $queryCount = count($timings);
  73. $queryTime = number_format($this->getTotalQueryTime($timings) * 1000) . ' ms';
  74. return Yii::$app->view->render('panels/db/summary', [
  75. 'timings' => $this->calculateTimings(),
  76. 'panel' => $this,
  77. 'queryCount' => $queryCount,
  78. 'queryTime' => $queryTime,
  79. ]);
  80. }
  81. /**
  82. * @inheritdoc
  83. */
  84. public function getDetail()
  85. {
  86. $searchModel = new Db();
  87. $dataProvider = $searchModel->search(Yii::$app->request->getQueryParams(), $this->getModels());
  88. return Yii::$app->view->render('panels/db/detail', [
  89. 'panel' => $this,
  90. 'dataProvider' => $dataProvider,
  91. 'searchModel' => $searchModel,
  92. 'hasExplain' => $this->hasExplain()
  93. ]);
  94. }
  95. /**
  96. * Calculates given request profile timings.
  97. *
  98. * @return array timings [token, category, timestamp, traces, nesting level, elapsed time]
  99. */
  100. public function calculateTimings()
  101. {
  102. if ($this->_timings === null) {
  103. $this->_timings = Yii::getLogger()->calculateTimings(isset($this->data['messages']) ? $this->data['messages'] : []);
  104. }
  105. return $this->_timings;
  106. }
  107. /**
  108. * @inheritdoc
  109. */
  110. public function save()
  111. {
  112. return ['messages' => $this->getProfileLogs()];
  113. }
  114. /**
  115. * Returns all profile logs of the current request for this panel. It includes categories such as:
  116. * 'yii\db\Command::query', 'yii\db\Command::execute'.
  117. * @return array
  118. */
  119. public function getProfileLogs()
  120. {
  121. $target = $this->module->logTarget;
  122. return $target->filterMessages($target->messages, Logger::LEVEL_PROFILE, ['yii\db\Command::query', 'yii\db\Command::execute']);
  123. }
  124. /**
  125. * Returns total query time.
  126. *
  127. * @param array $timings
  128. * @return integer total time
  129. */
  130. protected function getTotalQueryTime($timings)
  131. {
  132. $queryTime = 0;
  133. foreach ($timings as $timing) {
  134. $queryTime += $timing['duration'];
  135. }
  136. return $queryTime;
  137. }
  138. /**
  139. * Returns an array of models that represents logs of the current request.
  140. * Can be used with data providers such as \yii\data\ArrayDataProvider.
  141. * @return array models
  142. */
  143. protected function getModels()
  144. {
  145. if ($this->_models === null) {
  146. $this->_models = [];
  147. $timings = $this->calculateTimings();
  148. foreach ($timings as $seq => $dbTiming) {
  149. $this->_models[] = [
  150. 'type' => $this->getQueryType($dbTiming['info']),
  151. 'query' => $dbTiming['info'],
  152. 'duration' => ($dbTiming['duration'] * 1000), // in milliseconds
  153. 'trace' => $dbTiming['trace'],
  154. 'timestamp' => ($dbTiming['timestamp'] * 1000), // in milliseconds
  155. 'seq' => $seq,
  156. ];
  157. }
  158. }
  159. return $this->_models;
  160. }
  161. /**
  162. * Returns database query type.
  163. *
  164. * @param string $timing timing procedure string
  165. * @return string query type such as select, insert, delete, etc.
  166. */
  167. protected function getQueryType($timing)
  168. {
  169. $timing = ltrim($timing);
  170. preg_match('/^([a-zA-z]*)/', $timing, $matches);
  171. return count($matches) ? mb_strtoupper($matches[0], 'utf8') : '';
  172. }
  173. /**
  174. * Check if given queries count is critical according settings.
  175. *
  176. * @param integer $count queries count
  177. * @return boolean
  178. */
  179. public function isQueryCountCritical($count)
  180. {
  181. return (($this->criticalQueryThreshold !== null) && ($count > $this->criticalQueryThreshold));
  182. }
  183. /**
  184. * Returns array query types
  185. *
  186. * @return array
  187. * @since 2.0.3
  188. */
  189. public function getTypes()
  190. {
  191. return array_reduce(
  192. $this->_models,
  193. function ($result, $item) {
  194. $result[$item['type']] = $item['type'];
  195. return $result;
  196. },
  197. []
  198. );
  199. }
  200. /**
  201. * @return boolean Whether the DB component has support for EXPLAIN queries
  202. * @since 2.0.5
  203. */
  204. protected function hasExplain()
  205. {
  206. $db = $this->getDb();
  207. if (!($db instanceof \yii\db\Connection)) {
  208. return false;
  209. }
  210. switch ($db->getDriverName()) {
  211. case 'mysql':
  212. case 'sqlite':
  213. case 'pgsql':
  214. case 'cubrid':
  215. return true;
  216. default:
  217. return false;
  218. }
  219. }
  220. /**
  221. * Check if given query type can be explained.
  222. *
  223. * @param string $type query type
  224. * @return boolean
  225. *
  226. * @since 2.0.5
  227. */
  228. public static function canBeExplained($type)
  229. {
  230. return $type !== 'SHOW';
  231. }
  232. /**
  233. * Returns a reference to the DB component associated with the panel
  234. *
  235. * @return \yii\db\Connection
  236. * @since 2.0.5
  237. */
  238. public function getDb()
  239. {
  240. return Yii::$app->get($this->db);
  241. }
  242. }