1: <?php
2: /**
3: * Zend Framework
4: *
5: * LICENSE
6: *
7: * This source file is subject to the new BSD license that is bundled
8: * with this package in the file LICENSE.txt.
9: * It is also available through the world-wide-web at this URL:
10: * http://framework.zend.com/license/new-bsd
11: * If you did not receive a copy of the license and are unable to
12: * obtain it through the world-wide-web, please send an email
13: * to license@zend.com so we can send you a copy immediately.
14: *
15: * @category Zend
16: * @package Zend_Db
17: * @subpackage Profiler
18: * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
19: * @license http://framework.zend.com/license/new-bsd New BSD License
20: * @version $Id: Profiler.php 20096 2010-01-06 02:05:09Z bkarwin $
21: */
22:
23:
24: /**
25: * @category Zend
26: * @package Zend_Db
27: * @subpackage Profiler
28: * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
29: * @license http://framework.zend.com/license/new-bsd New BSD License
30: */
31: class Zend_Db_Profiler
32: {
33:
34: /**
35: * A connection operation or selecting a database.
36: */
37: const CONNECT = 1;
38:
39: /**
40: * Any general database query that does not fit into the other constants.
41: */
42: const QUERY = 2;
43:
44: /**
45: * Adding new data to the database, such as SQL's INSERT.
46: */
47: const INSERT = 4;
48:
49: /**
50: * Updating existing information in the database, such as SQL's UPDATE.
51: *
52: */
53: const UPDATE = 8;
54:
55: /**
56: * An operation related to deleting data in the database,
57: * such as SQL's DELETE.
58: */
59: const DELETE = 16;
60:
61: /**
62: * Retrieving information from the database, such as SQL's SELECT.
63: */
64: const SELECT = 32;
65:
66: /**
67: * Transactional operation, such as start transaction, commit, or rollback.
68: */
69: const TRANSACTION = 64;
70:
71: /**
72: * Inform that a query is stored (in case of filtering)
73: */
74: const STORED = 'stored';
75:
76: /**
77: * Inform that a query is ignored (in case of filtering)
78: */
79: const IGNORED = 'ignored';
80:
81: /**
82: * Array of Zend_Db_Profiler_Query objects.
83: *
84: * @var array
85: */
86: protected $_queryProfiles = array();
87:
88: /**
89: * Stores enabled state of the profiler. If set to False, calls to
90: * queryStart() will simply be ignored.
91: *
92: * @var boolean
93: */
94: protected $_enabled = false;
95:
96: /**
97: * Stores the number of seconds to filter. NULL if filtering by time is
98: * disabled. If an integer is stored here, profiles whose elapsed time
99: * is less than this value in seconds will be unset from
100: * the self::$_queryProfiles array.
101: *
102: * @var integer
103: */
104: protected $_filterElapsedSecs = null;
105:
106: /**
107: * Logical OR of any of the filter constants. NULL if filtering by query
108: * type is disable. If an integer is stored here, it is the logical OR of
109: * any of the query type constants. When the query ends, if it is not
110: * one of the types specified, it will be unset from the
111: * self::$_queryProfiles array.
112: *
113: * @var integer
114: */
115: protected $_filterTypes = null;
116:
117: /**
118: * Class constructor. The profiler is disabled by default unless it is
119: * specifically enabled by passing in $enabled here or calling setEnabled().
120: *
121: * @param boolean $enabled
122: * @return void
123: */
124: public function __construct($enabled = false)
125: {
126: $this->setEnabled($enabled);
127: }
128:
129: /**
130: * Enable or disable the profiler. If $enable is false, the profiler
131: * is disabled and will not log any queries sent to it.
132: *
133: * @param boolean $enable
134: * @return Zend_Db_Profiler Provides a fluent interface
135: */
136: public function setEnabled($enable)
137: {
138: $this->_enabled = (boolean) $enable;
139:
140: return $this;
141: }
142:
143: /**
144: * Get the current state of enable. If True is returned,
145: * the profiler is enabled.
146: *
147: * @return boolean
148: */
149: public function getEnabled()
150: {
151: return $this->_enabled;
152: }
153:
154: /**
155: * Sets a minimum number of seconds for saving query profiles. If this
156: * is set, only those queries whose elapsed time is equal or greater than
157: * $minimumSeconds will be saved. To save all queries regardless of
158: * elapsed time, set $minimumSeconds to null.
159: *
160: * @param integer $minimumSeconds OPTIONAL
161: * @return Zend_Db_Profiler Provides a fluent interface
162: */
163: public function setFilterElapsedSecs($minimumSeconds = null)
164: {
165: if (null === $minimumSeconds) {
166: $this->_filterElapsedSecs = null;
167: } else {
168: $this->_filterElapsedSecs = (integer) $minimumSeconds;
169: }
170:
171: return $this;
172: }
173:
174: /**
175: * Returns the minimum number of seconds for saving query profiles, or null if
176: * query profiles are saved regardless of elapsed time.
177: *
178: * @return integer|null
179: */
180: public function getFilterElapsedSecs()
181: {
182: return $this->_filterElapsedSecs;
183: }
184:
185: /**
186: * Sets the types of query profiles to save. Set $queryType to one of
187: * the Zend_Db_Profiler::* constants to only save profiles for that type of
188: * query. To save more than one type, logical OR them together. To
189: * save all queries regardless of type, set $queryType to null.
190: *
191: * @param integer $queryTypes OPTIONAL
192: * @return Zend_Db_Profiler Provides a fluent interface
193: */
194: public function setFilterQueryType($queryTypes = null)
195: {
196: $this->_filterTypes = $queryTypes;
197:
198: return $this;
199: }
200:
201: /**
202: * Returns the types of query profiles saved, or null if queries are saved regardless
203: * of their types.
204: *
205: * @return integer|null
206: * @see Zend_Db_Profiler::setFilterQueryType()
207: */
208: public function getFilterQueryType()
209: {
210: return $this->_filterTypes;
211: }
212:
213: /**
214: * Clears the history of any past query profiles. This is relentless
215: * and will even clear queries that were started and may not have
216: * been marked as ended.
217: *
218: * @return Zend_Db_Profiler Provides a fluent interface
219: */
220: public function clear()
221: {
222: $this->_queryProfiles = array();
223:
224: return $this;
225: }
226:
227: /**
228: * @param integer $queryId
229: * @return integer or null
230: */
231: public function queryClone(Zend_Db_Profiler_Query $query)
232: {
233: $this->_queryProfiles[] = clone $query;
234:
235: end($this->_queryProfiles);
236:
237: return key($this->_queryProfiles);
238: }
239:
240: /**
241: * Starts a query. Creates a new query profile object (Zend_Db_Profiler_Query)
242: * and returns the "query profiler handle". Run the query, then call
243: * queryEnd() and pass it this handle to make the query as ended and
244: * record the time. If the profiler is not enabled, this takes no
245: * action and immediately returns null.
246: *
247: * @param string $queryText SQL statement
248: * @param integer $queryType OPTIONAL Type of query, one of the Zend_Db_Profiler::* constants
249: * @return integer|null
250: */
251: public function queryStart($queryText, $queryType = null)
252: {
253: if (!$this->_enabled) {
254: return null;
255: }
256:
257: // make sure we have a query type
258: if (null === $queryType) {
259: switch (strtolower(substr(ltrim($queryText), 0, 6))) {
260: case 'insert':
261: $queryType = self::INSERT;
262: break;
263: case 'update':
264: $queryType = self::UPDATE;
265: break;
266: case 'delete':
267: $queryType = self::DELETE;
268: break;
269: case 'select':
270: $queryType = self::SELECT;
271: break;
272: default:
273: $queryType = self::QUERY;
274: break;
275: }
276: }
277:
278: /**
279: * @see Zend_Db_Profiler_Query
280: */
281: require_once 'Zend/Db/Profiler/Query.php';
282: $this->_queryProfiles[] = new Zend_Db_Profiler_Query($queryText, $queryType);
283:
284: end($this->_queryProfiles);
285:
286: return key($this->_queryProfiles);
287: }
288:
289: /**
290: * Ends a query. Pass it the handle that was returned by queryStart().
291: * This will mark the query as ended and save the time.
292: *
293: * @param integer $queryId
294: * @throws Zend_Db_Profiler_Exception
295: * @return void
296: */
297: public function queryEnd($queryId)
298: {
299: // Don't do anything if the Zend_Db_Profiler is not enabled.
300: if (!$this->_enabled) {
301: return self::IGNORED;
302: }
303:
304: // Check for a valid query handle.
305: if (!isset($this->_queryProfiles[$queryId])) {
306: /**
307: * @see Zend_Db_Profiler_Exception
308: */
309: require_once 'Zend/Db/Profiler/Exception.php';
310: throw new Zend_Db_Profiler_Exception("Profiler has no query with handle '$queryId'.");
311: }
312:
313: $qp = $this->_queryProfiles[$queryId];
314:
315: // Ensure that the query profile has not already ended
316: if ($qp->hasEnded()) {
317: /**
318: * @see Zend_Db_Profiler_Exception
319: */
320: require_once 'Zend/Db/Profiler/Exception.php';
321: throw new Zend_Db_Profiler_Exception("Query with profiler handle '$queryId' has already ended.");
322: }
323:
324: // End the query profile so that the elapsed time can be calculated.
325: $qp->end();
326:
327: /**
328: * If filtering by elapsed time is enabled, only keep the profile if
329: * it ran for the minimum time.
330: */
331: if (null !== $this->_filterElapsedSecs && $qp->getElapsedSecs() < $this->_filterElapsedSecs) {
332: unset($this->_queryProfiles[$queryId]);
333: return self::IGNORED;
334: }
335:
336: /**
337: * If filtering by query type is enabled, only keep the query if
338: * it was one of the allowed types.
339: */
340: if (null !== $this->_filterTypes && !($qp->getQueryType() & $this->_filterTypes)) {
341: unset($this->_queryProfiles[$queryId]);
342: return self::IGNORED;
343: }
344:
345: return self::STORED;
346: }
347:
348: /**
349: * Get a profile for a query. Pass it the same handle that was returned
350: * by queryStart() and it will return a Zend_Db_Profiler_Query object.
351: *
352: * @param integer $queryId
353: * @throws Zend_Db_Profiler_Exception
354: * @return Zend_Db_Profiler_Query
355: */
356: public function getQueryProfile($queryId)
357: {
358: if (!array_key_exists($queryId, $this->_queryProfiles)) {
359: /**
360: * @see Zend_Db_Profiler_Exception
361: */
362: require_once 'Zend/Db/Profiler/Exception.php';
363: throw new Zend_Db_Profiler_Exception("Query handle '$queryId' not found in profiler log.");
364: }
365:
366: return $this->_queryProfiles[$queryId];
367: }
368:
369: /**
370: * Get an array of query profiles (Zend_Db_Profiler_Query objects). If $queryType
371: * is set to one of the Zend_Db_Profiler::* constants then only queries of that
372: * type will be returned. Normally, queries that have not yet ended will
373: * not be returned unless $showUnfinished is set to True. If no
374: * queries were found, False is returned. The returned array is indexed by the query
375: * profile handles.
376: *
377: * @param integer $queryType
378: * @param boolean $showUnfinished
379: * @return array|false
380: */
381: public function getQueryProfiles($queryType = null, $showUnfinished = false)
382: {
383: $queryProfiles = array();
384: foreach ($this->_queryProfiles as $key => $qp) {
385: if ($queryType === null) {
386: $condition = true;
387: } else {
388: $condition = ($qp->getQueryType() & $queryType);
389: }
390:
391: if (($qp->hasEnded() || $showUnfinished) && $condition) {
392: $queryProfiles[$key] = $qp;
393: }
394: }
395:
396: if (empty($queryProfiles)) {
397: $queryProfiles = false;
398: }
399:
400: return $queryProfiles;
401: }
402:
403: /**
404: * Get the total elapsed time (in seconds) of all of the profiled queries.
405: * Only queries that have ended will be counted. If $queryType is set to
406: * one or more of the Zend_Db_Profiler::* constants, the elapsed time will be calculated
407: * only for queries of the given type(s).
408: *
409: * @param integer $queryType OPTIONAL
410: * @return float
411: */
412: public function getTotalElapsedSecs($queryType = null)
413: {
414: $elapsedSecs = 0;
415: foreach ($this->_queryProfiles as $key => $qp) {
416: if (null === $queryType) {
417: $condition = true;
418: } else {
419: $condition = ($qp->getQueryType() & $queryType);
420: }
421: if (($qp->hasEnded()) && $condition) {
422: $elapsedSecs += $qp->getElapsedSecs();
423: }
424: }
425: return $elapsedSecs;
426: }
427:
428: /**
429: * Get the total number of queries that have been profiled. Only queries that have ended will
430: * be counted. If $queryType is set to one of the Zend_Db_Profiler::* constants, only queries of
431: * that type will be counted.
432: *
433: * @param integer $queryType OPTIONAL
434: * @return integer
435: */
436: public function getTotalNumQueries($queryType = null)
437: {
438: if (null === $queryType) {
439: return count($this->_queryProfiles);
440: }
441:
442: $numQueries = 0;
443: foreach ($this->_queryProfiles as $qp) {
444: if ($qp->hasEnded() && ($qp->getQueryType() & $queryType)) {
445: $numQueries++;
446: }
447: }
448:
449: return $numQueries;
450: }
451:
452: /**
453: * Get the Zend_Db_Profiler_Query object for the last query that was run, regardless if it has
454: * ended or not. If the query has not ended, its end time will be null. If no queries have
455: * been profiled, false is returned.
456: *
457: * @return Zend_Db_Profiler_Query|false
458: */
459: public function getLastQueryProfile()
460: {
461: if (empty($this->_queryProfiles)) {
462: return false;
463: }
464:
465: end($this->_queryProfiles);
466:
467: return current($this->_queryProfiles);
468: }
469:
470: }
471:
472: