ตรวจดูและวิเคราะห์ประสิทธิภาพด้วย Profiler

Profiler นั้นการทำงานของมันคือการตรวจดูและวิเคราะห์ประสิทธิภาพ โดยอาจจะทำงานตรวจสำหรับแอพพลิเคชั่นของคุณที่เป็น native PHP หรือใช้ร่วมกับ framework ใดๆก็ได้. การนำมาใช้งานนั้นไม่ยาก เพราะส่วนที่เป็นอัตโนมัติก็มีมากกว่าครึ่งเสียแล้ว การปรับแต่งก็แทบจะไม่ต้องไปแก้ที่โค้ดของ Profiler โดยตรง ส่งผลให้การตามอัพเดทโค้ดของเราทำได้ง่ายๆไม่ต้องกังวลเรื่องการตามแก้ไขโค้ดใน Profiler ที่เคยทำไว้จะถูกเปลี่ยนแปลง.

Download หรือติดตั้งผ่าน Composer

อ้างอิง เอกสาร API

License: MIT

ตัวอย่างการใช้งาน

ตัวอย่างที่จะนำเสนอให้ดูต่อไปนี้เป็นตัวอย่างการทำงานอย่างเต็มรูปแบบ.
test.php


<?php
// กำหนด session_start เพื่อทดสอบการใช้ session
session_start();

// ถ้าไม่ได้ติดตั้งผ่าน Composer จำเป็นต้อง require ไฟล์เหล่านี้
require dirname(__DIR__).'/Rundiz/Profiler/ProfilerBase.php';
require dirname(__DIR__).'/Rundiz/Profiler/Profiler.php';

$profiler = new \Rundiz\Profiler\Profiler();
// ลงทะเบียนเซ็คชั่นต่างๆที่จะตรวจวิเคราะห์และแสดงผล.
$profiler->Console->registerLogSections(array('Logs', 'Time Load', 'Memory Usage', 'Database', 'Files', 'Session', 'Get', 'Post'));

// require ไฟล์ที่เขียนฟังก์ชั่นไว้เพื่อทดสอบ
require __DIR__.'/common-test-functions.php';
// require ไฟล์สำหรับเชื่อมต่อฐานข้อมูล
require __DIR__.'/db-functions.php';

// เชื่อมต่อฐานข้อมูล
$dbh = rdpConnectDb($configdb);

?>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Rundiz\Profiler test</title>
        
        <link rel="stylesheet" href="reset.css">
        <link rel="stylesheet" href="main.css">
    </head>
    <body>
        <h1>Rundiz\Profiler test</h1>
        <p>This page test followings:</p>
        <ul>
            <li>Log</li>
            <li>Time Load</li>
            <li>Memory usage</li>
            <li>Database</li>
            <li>Files</li>
            <li>Session</li>
            <li>Get</li>
            <li>Post</li>
        </ul>

        <?php
        // เงื่อนไข ทำงานเมื่อมีการกด submit method="post" เท่านั้น.
        if ($_POST) {
            // ทดสอบแสดงผลจากฐานข้อมูลเฉยๆ
        ?> 
        <h2>Test database</h2>
        <?php
            $sql = 'SELECT * FROM `people` LIMIT 0, 20';
            $sth = rdpDbQuery($dbh, $sql);
            $results = $sth->fetchAll(\PDO::FETCH_OBJ);
        ?> 
        <p>Basic listing; Limits <?php echo $sth->rowCount(); ?> items.</p>
        <?php 
            // list data test.
            if (is_array($results) && !empty($results)) {
        ?> 
        <table class="table">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>Name</th>
                    <th>Address</th>
                    <th>Date</th>
                </tr>
            </thead>
            <tbody>
        <?php
                foreach ($results as $row) {
        ?> 
                <tr>
                    <td><?php echo $row->id; ?></td>
                    <td><?php echo $row->name; ?></td>
                    <td><?php echo $row->address; ?></td>
                    <td><?php echo date('Y-m-d', $row->date); ?></td>
                </tr>
        <?php
                }// endforeach;
        ?> 
            </tbody>
        </table>
        <?php
            }// endif list data test.
            unset($results, $sql, $sth);
            // จบการทดสอบแสดงผลจากฐานข้อมูลเฉยๆ
        ?> 
        <h3>Test queries</h3>
        <?php
        // ทดสอบการ query ข้อมูลแบบต่างๆเพื่อทำการ profile database
        $queries = array(
            'SELECT * FROM people',
            'SELECT * FROM people WHERE name LIKE \'%ben%\'',
            'SELECT * FROM people WHERE id > 10',
            'SELECT * FROM people ORDER BY id DESC',
            'SELECT * FROM people ORDER BY RAND()',
            'SELECT * FROM people WHERE name LIKE \'%no-one-have-this-name%\'',
        );
        foreach ($queries as $statement) {
            $sth = rdpDbQuery($dbh, $statement);
            echo '<p>Querying: <code>'.htmlspecialchars($statement).'</code> have '.$sth->rowCount().' rows.</p>'."\n";
        }
        unset($queries, $statement);
        // จบการทดสอบการ query ข้อมูลแบบต่างๆเพื่อทำการ profile database
        ?> 
        <p>End test db profiling.</p>
        <?php
        }// endif $_POST db profiling test.
        ?> 

        <!-- ทำการ profiling แบบธรรมดาทั่วไป (Log, Time Load, Memory Usage). -->
        <hr>

        <?php
        // ทำการ profiling แบบธรรมดาทั่วไป (Log, Time Load, Memory Usage)
        rdpBasicLogs($profiler);
        rdpTimeLoadLogs($profiler);
        rdpMemoryUsage($profiler);

        // เรียก gatherAll() ก่อนเพื่อทำการตรวจดูผ่าน dumptest(). การ dumptest() นั้นไม่จำเป็นในงาน production จริงๆ มันไม่ต้องมีก็ได้.
        $profiler->gatherAll();

        // เงื่อนไข ทำงานเมื่อมีการกด submit method="post" เท่านั้น.
        if ($_POST) {
            // สำหรับตรวจสอบดูว่า Profiler รับข้อมูลอะไรเอาไว้แล้วบ้าง. การเรียก dumptest() จะต้องเรียก gatherAll() ก่อน. การ dumptest() นั้นไม่จำเป็นในงาน production จริงๆ มันไม่ต้องมีก็ได้.
            echo '<pre class="profiler-data-dump-test">'.htmlspecialchars(print_r($profiler->dumptest(), true)).'</pre>';
            echo "\n\n\n";
        } else {
            // เงื่อนไขไม่ได้อยู่ใน method POST ให้กำหนด session และแสดงปุ่ม submit method="post" เพื่อเริ่มทดสอบ.
            $_SESSION['session1'] = 'val1';
            $_SESSION['session2'] = 'val2';
            $_SESSION['session3'] = array(
                'sub1' => array(
                    'sub1.1' => 'val1.1',
                    'sub1.2' => 'val1.2',
                ),
                'sub2' => 'val2',
            );
        ?> 
        <!--แสดงปุ่ม submit method="post" สำหรับเริ่มทดสอบ-->
        <form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>?query1=val1&amp;query2=val2&amp;query3[1]=val3-1&amp;query3[2]=val3-2&amp;query4[1]=val4[1]&amp;query4[2]=val4[2]&amp;query4[2][1]=val4[2][1]">
            <input type="hidden" name="post1" value="val1">
            <input type="hidden" name="post2" value="val2">
            <input type="hidden" name="post3[]" value="val3-1">
            <input type="hidden" name="post3[]" value="val3-2">
            <input type="hidden" name="post4[]" value="val4[1]">
            <input type="hidden" name="post4[]" value="val4[2]">
            <input type="hidden" name="post5[1][1]" value="val5[1][1]">
            <input type="hidden" name="post5[1][2]" value="val5[1][2]">
            <input type="hidden" name="post5[2][1]" value="val5[2][1]">
            <input type="hidden" name="post5[2][2]" value="val5[2][2]">
            <input type="hidden" name="post_html" value="&lt;div&gt;">
            <button type="submit">Begin test!</button>
        </form>
        <?php
        }

        // include ไฟล์แสดงผล profile database เพื่อใช้กับการใช้งาน database ของคุณเอง
        include __DIR__.'/display-profiler-db.php';
        $rdpDisplayProfilerDb = new rdpDisplayProfilerDb();

        // แสดงหน้าต่างข้อมูล profiler
        echo $profiler->display($dbh, array($rdpDisplayProfilerDb, 'display'));
        // ในกรณีที่คำสั่งแสดง profile database เป็น function ไม่ได้เป็น class เหมือนตัวอย่าง, คุณไม่ต้องใช้ array() ครอบ ทำเหมือนด้านล่างนี้เลย.
        // echo $profiler->display($dbh, 'displayProfilerDbFunction');
        unset($dbh, $rdpDisplayProfilerDb);
        ?>
    </body>
</html>

common-test-functions.php


<?php
/**
 * test basic logs
 * 
 * @param \Rundiz\Profiler\Profiler $profiler
 * @throws Exception
 */
function rdpBasicLogs($profiler)
{
    try {
        $profiler->Console->log('debug', 'Debug log or normal log data.');
        $profiler->Console->log('info', array('Simple' => 'This array', 'Array' => 'Only have 1 level.'));
        $profiler->Console->log('info', array('What' => 'Test log array data', 'Values' => array('Name' => 'Vee', 'Last' => 'W')));

        $testobj = new \stdClass();
        $testobj->newprop = 'new_val';
        $testobj->arrprop = array('this' => 'is array', 'value' => array('array' => 'value of object property'));
        $profiler->Console->log('notice', $testobj, __FILE__, __LINE__);
        unset($testobj);

        $profiler->Console->log('warning', 'End log test. Start throwing errors.');
        throw new Exception('Some thing error happens');
    } catch (Exception $ex) {
        $profiler->Console->log('error', 'Something error here. '.$ex->getMessage().' '.$ex->getFile().': '.$ex->getLine(), __FILE__, __LINE__);
        $profiler->Console->log('critical', 'Something critical here. '.$ex->getMessage().' '.$ex->getFile().': '.$ex->getLine(), __FILE__, __LINE__);
        $profiler->Console->log('alert', 'Something alert here. '.$ex->getMessage().' '.$ex->getFile().': '.$ex->getLine(), __FILE__, __LINE__);
        $profiler->Console->log('emergency', 'Something emergency here. '.$ex->getMessage().' '.$ex->getFile().': '.$ex->getLine(), __FILE__, __LINE__);
    }
}// rdpBasicLogs


/**
 * test memory usage logs
 * 
 * @param \Rundiz\Profiler\Profiler $profiler
 */
function rdpMemoryUsage($profiler)
{
    $data = '';
    $long_str = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam gravida lacinia sapien id interdum. Ut diam lacus, maximus non augue vel, malesuada tristique turpis. Donec non porta ipsum. Suspendisse non tincidunt leo. Aliquam sed volutpat odio, et suscipit velit. Mauris fermentum ut ex sit amet sagittis. Mauris egestas molestie eleifend. In condimentum eu diam gravida scelerisque. Fusce nec erat vel augue commodo pharetra efficitur quis orci. Donec vitae libero venenatis, consequat velit quis, convallis libero. Cras lectus magna, consectetur vitae dolor ac, luctus ornare felis. Integer nec auctor nulla. Nulla facilisi. Pellentesque commodo interdum felis, ac sollicitudin tellus cursus lobortis. Nulla porta, erat eu aliquam auctor, mi ipsum egestas risus, volutpat vehicula sapien ipsum eu quam. Nunc ut neque sit amet elit dapibus elementum sed ornare tellus. Phasellus blandit turpis risus, eget condimentum odio luctus in. Nullam consectetur lacus sodales nunc tincidunt, eget malesuada eros tristique. Suspendisse elementum augue a quam ultrices sodales. Donec porta augue gravida tortor consectetur, non congue tellus porttitor. In hac habitasse platea dictumst. Maecenas non imperdiet ipsum. Suspendisse auctor luctus ligula nec vestibulum. Sed feugiat magna ac purus consectetur, ac ultricies augue convallis. Donec vel felis et libero eleifend volutpat. Mauris pellentesque nec sem vitae cursus. Vestibulum egestas, risus vel tincidunt vehicula, est dui pellentesque nisi, in facilisis erat odio ac arcu. Vivamus ac ante metus. Proin pulvinar rutrum ligula, eget rutrum velit mollis id. Praesent lacinia purus et risus consectetur sodales. Duis lectus mauris, varius eu pellentesque sit amet, ultrices maximus est. Mauris mattis dapibus nisl ut placerat. Aliquam maximus egestas commodo. Vestibulum commodo mi magna, ut suscipit elit dapibus eget. Fusce vitae est quis ex feugiat maximus ac eget massa. Vestibulum porttitor urna eget diam finibus, nec porttitor est egestas. Sed sem massa, mattis sit amet pretium et, laoreet id ante.';

    // use debug back trace just for set file and line where it call this function.
    $backtrace = debug_backtrace();
    if (is_array($backtrace)) {
        foreach ($backtrace as $items) {
            if (is_array($items) && array_key_exists('file', $items) && array_key_exists('line', $items)) {
                $file = $items['file'];
                $line = $items['line'];
                break;
            }
        }
    }
    unset($backtrace, $items);

    for ($i = 0; $i <= 10; $i++) {
        $data .= $long_str . "\r\n\r\n";
        $profiler->Console->memoryUsage('Loop round '.$i.' memory usage log at file:'.__FILE__.': '.__LINE__, $file, $line);
    }

    unset($data);
}// rdpMemoryUsage


/**
 * test time load logs
 * 
 * @param \Rundiz\Profiler\Profiler $profiler
 */
function rdpTimeLoadLogs($profiler)
{
    $file = '';
    $line = '';
    $backtrace = debug_backtrace();
    if (is_array($backtrace)) {
        foreach ($backtrace as $items) {
            if (is_array($items) && array_key_exists('file', $items) && array_key_exists('line', $items)) {
                $file = $items['file'];
                $line = $items['line'];
                break;
            }
        }
    }
    unset($backtrace, $items);

    $profiler->Console->timeload('Time taken to this line '.__FILE__.': '.__LINE__, $file, $line);
    $profiler->Console->timeload('Time taken to this line '.__FILE__.': '.__LINE__, $file, $line);
    $profiler->Console->timeload('Time taken to this line '.__FILE__.': '.__LINE__, $file, $line);
    $profiler->Console->timeload('Time taken to this line '.__FILE__.': '.__LINE__, $file, $line);
    usleep(100000);
    $profiler->Console->timeload('After sleep. Time taken to this line '.__FILE__.': '.__LINE__, $file, $line);
    $profiler->Console->timeload('Time taken to this line '.__FILE__.': '.__LINE__, $file, $line);
}// rdpTimeLoadLogs

db-functions.php
การ profile database! คุณต้องเรียกกลับไปยัง $profiler->Console->database() เพื่อบันทึก sql statement เช่น (SELECT * FROM table) ไปไว้ในประวัติเพื่อใช้ในการวิเคราะห์. ทั้งหมดที่ต้องทำสำหรับการ profile database มีอยู่แค่ตรงนี้!


<?php
/**
 * connect to db.
 * 
 * @param array $config
 * @return \PDO
 */
function rdpConnectDb(array $config)
{
    // DO NOT enter db config here!
    $host = '';
    $dbname = '';
    $username = '';
    $password = '';

    extract($config);

    try {
        $dbh = new \PDO(
            'mysql:host='.$host.';dbname='.$dbname, $username, $password,
            array(\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'')
        );

        return $dbh;
    } catch (\PDOException $e) {
        echo '<div class="alert-error">Connection failed: ' . $e->getMessage(). '</div>' . "\n";
    }
}// rdpConnectDb


/**
 * query the data in db. fetch mode is class.
 * 
 * @param \PDO $dbh
 * @param string $statement
 * @param array $prepare_data
 * @return \PDOStatement
 */
function rdpDbQuery(\PDO $dbh, $statement, array $prepare_data = array())
{
    // try to get profiler variable into this function for profiling.
    $profiler = $GLOBALS['profiler'];
    // set time start.
    $time_start = $profiler->getMicrotime();

    // normal db query.
    $sth = $dbh->prepare($statement);
    $sth->execute(array());

    // ส่วนนี้สำคัญสำหรับการ profile database! คุณต้องเรียกกลับไปยัง `$profiler->Console->database()` เพื่อบันทึก sql statement เช่น (SELECT * FROM table) ไปไว้ในประวัติเพื่อใช้ในการวิเคราะห์. ทั้งหมดที่ต้องทำสำหรับการ profile database มีอยู่แค่ตรงนี้!
    $profiler->Console->database($statement, $time_start);

    // clear and return query result.
    unset($time_start);
    return $sth;
}// rdpDbQuery

display-profiler-db.php
ส่วนนี้มี class ที่จะถูกนำไปใช้ในการแสดงผล profile database


<?php
class rdpDisplayProfilerDb
{


    public function display()
    {
        list($profiler, $dbh, $data_values) = func_get_args();

        if (is_array($data_values)) {
            if (array_key_exists('time_start', $data_values) && array_key_exists('time_end', $data_values)) {
                // แสดงเวลาที่ใช้ในการ query ข้อมูลนี้เท่านั้น.
                echo '<div class="rdprofiler-log-db-timetake">'."\n";
                echo $profiler->getReadableTime(($data_values['time_end']-$data_values['time_start'])*1000);
                echo '</div>'."\n";
            }
            if (array_key_exists('memory', $data_values)) {
                // แสดงหน่วยความจำที่ใช้มาจนถึงการ query ข้อมูลนี้.
                echo '<div class="rdprofiler-log-memory">';
                if (isset($number)) {
                    echo $number->fromBytes($data_values['memory']);
                } else {
                    echo $profiler->getReadableFileSize($data_values['memory']);
                }
                echo '</div>'."\n";
            }
        }

        $sth = rdpDbQuery($dbh, 'EXPLAIN '.$data_values['data']);
        if ($sth) {
            echo '<div class="rdprofiler-log-newrow">'."\n";
            echo '<div class="rdprofiler-log-db-explain">'."\n";
            $results = $sth->fetchObject();
            if ($results) {
                foreach ($results as $key => $val) {
                    echo $key . ' = ' . $val;
                    if (end($results) != $val) {
                        echo ', ';
                    }
                }
            }
            unset($key, $results, $val);
            echo '</div>'."\n";
            echo '</div>'."\n";
        }

        if (is_array($data_values) && array_key_exists('call_trace', $data_values)) {
            echo '<div class="rdprofiler-log-newrow">'."\n";
            echo '<div class="rdprofiler-log-db-trace">'."\n";
            echo '<strong>Call trace:</strong><br>'."\n";
            foreach ($data_values['call_trace'] as $trace_item) {
                echo $trace_item['file'].', line '.$trace_item['line'].'<br>'."\n";
            }
            unset($trace_item);
            echo '</div>'."\n";
            echo '</div>'."\n";
        }

        unset($data_values, $dbh, $profiler, $sth);
    }// display


}

ตัวอย่างผลการทำงาน

okvee profiler screenshot

 

ใส่ความเห็น

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>