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

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

ดาวน์โหลด หรือติดตั้งผ่าน Composer

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

License: MIT

การทำงาน

จะขออธิบายการทำงานทีละขั้นละตอนก่อน โดยโปรดอ่านข้อความ comment ในโค้ดไปด้วย.

// กรณีไม่ได้ติดตั้งผ่าน Composer จะต้อง require file ต่อไปนี้.
require 'Rundiz/Profiler/ProfilerBase.php';
require 'Rundiz/Profiler/Profiler.php';

// เริ่มคลาส profiler
$profiler = new \Rundiz\Profiler\Profiler();
$profiler->Console->registerLogSections(['Logs', 'Time Load']);
// ในส่วนของ registerLogSections จะมีทั้งหมดคือ 'Logs', 'Time Load', 'Memory Usage', 'Database', 'Files', 'Session', 'Get', 'Post' จะใส่เฉพาะบางอย่างก็ได้ ไม่ต้องใส่หมดก็ได้
// จากนั้นสามารถนำตัวแปร $profiler ไปใช้ได้ทันที

การกำหนด Logs

การกำหนด Logs สามารถกำหนดผ่านตัวแปร $profiler->Console->log();

ระดับของการ log สามารถกำหนดได้ผ่านทาง argument ค่าแรกของ method log. โดยค่าที่รองรับทั้งหมดเป็นไปตามมาตรฐาน PSR กล่าวคือรองรับ debug, info, notice, warning, error, critical, alert, emergency

การ log ข้อความธรรมดา

$profiler->Console->log('debug', 'Debug log or normal log data.');

การ log ตัวแปรประเภทต่างๆก็ทำได้ เช่น array, object สามารถทำได้ 2 แบบ. แบบแรกคือจับวางลงไปใน argument ที่สองของ method log() เลยแบบนี้.

$profiler->Console->log('info', ['Test' => 'Test log array data', 'Values' => 'Only have 1 level.']);

แบบที่สองคือใช้ร่วมกับข้อความที่เป็นสตริง จะต้องทำผ่าน print_r($var, true); ดังตัวอย่างด้านล่าง.

$profiler->Console->log('info', 'This is string. '."\n".print_r(['Test' => 'Test log array data', 'Values' => 'Only have 1 level.'], true));

Log นี้จะไม่มีการบันทึกลงเป็นไฟล์ แต่มันใช้แสดงผลใน profiler เฉยๆ.

การกำหนด Time Load

การกำหนด Time Load หรือเวลาในการโหลด สามารถกำหนดผ่าน $profiler->Console->timeload(); ซึ่งจะรับค่า argument 4 ค่า. ค่าแรกคือข้อมูลหรือข้อความที่จะบันทึก, ค่าที่ 2 คือ ไฟล์ที่ทำงาน, ค่าที่ 3 คือบรรทัดในไฟล์นั้น, ค่าที่ 4 คือ matchKey. โดยตัว matchKey นี้จะต้องตรงกันระหว่างการบันทึก time load ครั้งแรกกับครั้งสุดท้าย แล้วตัว profiler จะคำณวนให้ว่าเวลาใช้ไปเท่าใด.

ค่า argument ที่ 2, 3, 4 จะไม่กำหนดก็ได้ โดยเฉพาะค่าที่ 4 ถ้าไม่กำหนดก็จะไม่มีการคำณวนเวลาที่ใช้ใน matchKey นั้นๆ แต่จะยังคงแสดงเวลาที่ใช้ไปทั้งหมดเป็นลำดับๆ.

// กำหนดแบบบันทึกเวลาที่ใช้ไปทั้งหมดธรรมดาๆเมื่อทำงานมาจนถึงบรรทัดนี้.
$profiler->Console->timeload('Time taken to this.');
// กำหนดให้เจาะจงลงไปอีกว่า ทำงานมาถึงไฟล์ใด บรรทัดใด.
$profiler->Console->timeload('Time taken to this line.', __FILE__, __LINE__);
// กำหนด matchKey เพื่อให้ profiler คำณวนเวลาที่ใช้เฉพาะจุดในภายหลัง
$profiler->Console->timeload('Before usleep. Time taken to this line.', __FILE__, __LINE__, 'commontest_usleep');
usleep(100000);
$profiler->Console->timeload('After usleep. Time taken to this line.', __FILE__, __LINE__, 'commontest_usleep');

การกำหนด Memory Usage

Memory Usage นั้นทำงานคล้ายๆ Time Load โดยมันรับ argument 4 ค่าเหมือนกันเป๊ะ เพียงแต่การแสดงผลจะแสดงออกมาเป็นจำนวนหน่วยความจำที่ใช้ไปทั้งหมด, ที่ใช้ไปตาม matchKey นั้นๆ.

// กำหนดหน่วยความจำที่ใช้ไปทั้งหมดธรรมดาๆเมื่อทำงานมาจนถึงบรรทัดนี้.
$profiler->Console->memoryUsage('Memory usage to this.');
// กำหนดเจาะจงลงไปจนถึงไฟล์และบรรทัด
$profiler->Console->memoryUsage('Memory usage to this line.', __FILE__, __LINE__);
// กำหนด matchKey เพื่อให้ profiler คำณวนหน่วยความจำที่ใช้เฉพาะจุดในภายหลัง
$profiler->Console->memoryUsage('Memory usage for this process.', __FILE__, __LINE__, 'imageprocessing');
// image processing source code here...
$profiler->Console->memoryUsage('Memory usage for this process.', __FILE__, __LINE__, 'imageprocessing');

การกำหนด Database

Database profiling นั้นจะทำงานค่อนข้างพิเศษแตกต่างกว่าอย่างอื่น คือมันจะต้องเก็บข้อมูลเป็น SQL statement เท่านั้น และเวลาหรือหน่วยความจำที่ใช้ก็จะเป็นแบบเฉพาะ query นั้นๆ ไม่มีแบบเรียงลำดับทั้งหมดลงมาแสดงอยู่ด้วยเหมือน Time Load และ Memory Usage.

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

// กำหนดเวลาเริ่มต้น
$time_start = $profiler->getMicrotime();
// กำหนดหน่วยความจำเริ่มต้น
$memory_start = memory_get_usage();

// sql statement
$sql = 'SELECT * FROM `table`';
$result = mysqli_query($link, $sql);

// ทำการเก็บข้อมูลเพื่อ profiling
$profiler->Console->database($sql, $time_start, $memory_start);

// clear ค่าเวลาและหน่วยความจำเริ่มต้นทิ้ง เพื่อเวลาจะเริ่มใหม่จะได้กำหนดใหม่ ไม่เอาของเดิมมาใช้ จะได้มีค่าเริ่มต้นจริงๆที่ถูกต้อง.
unset($memory_start, $time_start);

// ทำการแสดง query ไปตามปกติเหมือนที่คุณเคยทำมา

จากตัวอย่างด้านบน หากคุณใช้ PDO หรือ database class อื่นๆก็สามารถทำได้ เพียงแค่ทำการ execute query ให้มันได้ผลเรียบร้อยแล้วจึงค่อยเรียก $profiler->Console->database();.

การกำหนด Files, Session, Get, Post

ข้อมูลเหล่านี้ จะถูกรวบรวมมาโดยอัตโนมัติเมื่อคุณทำการเรียก $profiler->display();. ดังนั้นคุณไม่ต้องทำอะไรอีก เพียงแค่อย่าไปแก้ไขค่า $_SESSION, $_GET, $_POST ก็พอ.

การแสดงผล profiler

การแสดงผลนั้นก็เพียงแค่เรียก method $profiler->display();. เพียงแต่ว่ามันจะยุ่งยากเล็กน้อยตอนแสดงผล Database ซึ่งการแสดงผล profiler ฐานข้อมูลนั้น เรามีตัวอย่างให้แล้วทั้งแบบ PDO และ mysqli function. โปรดดูตัวอย่างการใช้งานด้านล่างเพิ่มเติม.

ค่าใน method display นั้นมี 2 ค่า โดยค่าแรกเป็นตัวแปรที่ทำงานกับฐานข้อมูล เช่น mysqli link resource, หรือ \PDO object. ค่าที่สองคือค่าเรียกกลับสำหรับฟังก์ชั่นหรือคลาสแสดงผล profiler database.

ตัวอย่างการแสดงผล profiler กับ mysqli และค่าเรียกกลับที่เป็น function. ทั้งนี้คุณสามารถใช้การเชื่อมต่อฐานข้อมูลเป็น mysqli หรืออื่นๆก็ได้อีกเช่นกัน ไม่มีข้อจำกัดเอาไว้.

// $link ได้มาจาก mysqli_connect();
echo $profiler->display($link, 'displayProfilerDb');

function displayProfilerDb(\Rundiz\Profiler\Profiler $profiler, $link, array $data_values)
{
    // display database profiler source code here.
}

ตัวอย่างการแสดงผล profiler กับ PDO และค่าเรียกกลับที่เป็น class&method. ทั้งนี้คุณสามารถใช้การเชื่อมต่อฐานข้อมูลเป็น mysqli หรืออื่นๆก็ได้อีกเช่นกัน ไม่มีข้อจำกัดเอาไว้.

$rdpDisplayProfilerDb = new rdpDisplayProfilerDb();

// $dbh ได้มาจาก new \PDO();
echo $profiler->display($dbh, [$rdpDisplayProfilerDb, 'display']);

class rdpDisplayProfilerDb
{
    public function display()
    {
        list($profiler, $dbh, $data_values) = func_get_args();
        // display database profiler source code here.
    }
}

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

ตัวอย่างที่จะนำเสนอให้ดูต่อไปนี้เป็นตัวอย่างการทำงานอย่างเต็มรูปแบบ. ทั้งนี้ไฟล์เหล่านี้มีให้อยู่แล้วในแพ็คเกจที่จะดาวน์โหลดไป โดยมันจะอยู่ในโฟลเดอร์ tests/via-http และจะมีมากกว่าที่เห็นนี้.
test.php

<?php
// remove this line and read important guide below to start test db profiling.
session_start();

// !important!
// please follow these step before you begins test in full options.
// 1. enter your configuration here. this is for connect to db.
$configdb = [];
$configdb['host'] = 'localhost';
$configdb['dbname'] = '';
$configdb['username'] = '';
$configdb['password'] = '';
// 2. create database where you specify in the config above.
// 3. import data that contain create table and insert in the file 'test-db.sql' into mysql, mariadb server.


// require the class files. you may no need these if you install via composer.
require dirname(dirname(__DIR__)).'/Rundiz/Profiler/ProfilerBase.php';
require dirname(dirname(__DIR__)).'/Rundiz/Profiler/Profiler.php';

$profiler = new \Rundiz\Profiler\Profiler();
$profiler->Console->registerLogSections(['Logs', 'Time Load', 'Memory Usage', 'Database', 'Files', 'Session', 'Get', 'Post']);

// -----------------------------------------------------------------------------------------------------
// lazy to write same test on every page, use common test functions
// you can change this to other coding style in your real project.
require __DIR__.'/common-test-functions.php';

// -----------------------------------------------------------------------------------------------------
// db profiling test.
// include file for test db.
require __DIR__.'/db-functions.php';
// connect to db.
$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
        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
        $queries = [
            '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%\'',
            'SHOW CHARACTER SET',
            'SHOW TABLES',
            'SET CHARACTER SET utf8',
            'DESCRIBE people',
            'EXPLAIN people',
        ];
        foreach ($queries as $statement) {
            $sth = rdpDbQuery($dbh, $statement);
            echo '<p>Querying: <code>'.htmlspecialchars($statement).'</code> have '.$sth->rowCount().' rows.</p>'."\n";
        }
        unset($queries, $statement);
        ?> 
        <p>End test db profiling.</p>
        <?php
        }// endif $_POST db profiling test.
        ?> 

        <!-- normal profiling (anything else that is not db profiling. -->
        <hr>

        <?php

        rdpBasicLogs($profiler);
        rdpTimeLoadLogs($profiler);
        rdpMemoryUsage($profiler);

        $profiler->gatherAll();

        if ($_POST) {
            // just checking.
            echo '<pre class="profiler-data-dump-test">'.htmlspecialchars(print_r($profiler->dumptest(), true)).'</pre>';
            echo "\n\n\n";
        } else {
            $_SESSION['session1'] = 'val1';
            $_SESSION['session2'] = 'val2';
            $_SESSION['session3'] = [
                'sub1' => [
                    'sub1.1' => 'val1.1',
                    'sub1.2' => 'val1.2',
                ],
                'sub2' => 'val2',
            ];
        ?> 
        <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 class for display profiler db.
        include __DIR__.'/display-profiler-db.php';
        $rdpDisplayProfilerDb = new rdpDisplayProfilerDb();
        // display profiler window.
        echo $profiler->display($dbh, [$rdpDisplayProfilerDb, 'display']);
        // in case that display profiler db is function, you can use the line below.
        // echo $profiler->display($dbh, 'displayProfilerDbFunction');
        unset($dbh, $rdpDisplayProfilerDb);
        ?>
    </body>
</html>

common-test-function.php

<?php
/**
 * This file is for common test functions that will work in other pages.
 */


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

        $testobj = new \stdClass();
        $testobj->newprop = 'new_val';
        $testobj->arrprop = ['this' => 'is array', 'value' => ['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);

    $profiler->Console->memoryUsage('Before loop memory usage at file:'.__FILE__.': '.__LINE__, $file, $line, 'commontest_memoryusage_loop');

    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);
    }

    $profiler->Console->memoryUsage('After loop memory usage at file:'.__FILE__.': '.__LINE__, $file, $line, 'commontest_memoryusage_loop');


    unset($data);
}// rdpMemoryUsage


/**
 * test time load logs
 * 
 * @param \Rundiz\Profiler\Profiler $profiler
 */
function rdpTimeLoadLogs($profiler)
{
    $profiler->Console->timeload('Time at begins of time load logs test.');

    $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('Before usleep. Time taken to this line '.__FILE__.': '.__LINE__, $file, $line, 'commontest_usleep');
    usleep(100000);
    $profiler->Console->timeload('After usleep. Time taken to this line '.__FILE__.': '.__LINE__, $file, $line, 'commontest_usleep');
    $profiler->Console->timeload('Time taken to this line '.__FILE__.': '.__LINE__, $file, $line);

    for ($i = 1; $i <= 10; $i++) {
        $profiler->Console->timeload('Time taken to this loop ('.$i.') '.__FILE__.': '.__LINE__, $file, $line, 'commontest_timeloop_'.$i);
    }
    for ($i = 10; $i >= 1; $i--) {
        $profiler->Console->timeload('Time taken to this loop (end loop for collect self time'.$i.') '.__FILE__.': '.__LINE__, $file, $line, 'commontest_timeloop_'.$i);
    }

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

db-functions.php

<?php
/**
 * This file is for test database profiler functions.
 */


/**
 * 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,
            [\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 = [], $console_log = true)
{
    // try to get profiler variable into this function for profiling.
    /* @var $profiler \Rundiz\Profiler\Profiler */
    $profiler = $GLOBALS['profiler'];
    // set time start.
    $time_start = $profiler->getMicrotime();
    // set memory start.
    $memory_start = memory_get_usage();

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

    if ($console_log === true) {
        // call profiler->Console->database() to profiling this. that's all for database profiling!
        $profiler->Console->database($statement, $time_start, $memory_start);
    }

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

display-profiler-db.php

<?php
/**
 * This file is for displaying db profiler result.
 * 
 * @license http://opensource.org/licenses/MIT MIT
 */


/**
 * A callback class for display DB profiler.
 * 
 * You can copy and use this source code or change from PDO to how you query your database.
 * 
 * @author Vee W.
 */
class rdpDisplayProfilerDb
{


    /**
     * Display profiler db.<br>
     * This part is not accessible by any URL. it can be used via `$Profiler->display()` only.
     */
    public function display()
    {
        /* @var $dbh \PDO */
        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)) {
                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_end', $data_values) && array_key_exists('memory_start', $data_values) && is_int($data_values['memory_end']) && is_int($data_values['memory_start'])) {
                echo '<div class="rdprofiler-log-memory">';
                if (isset($number)) {
                    echo $number->fromBytes($data_values['memory_end']-$data_values['memory_start']);
                } else {
                    echo $profiler->getReadableFileSize($data_values['memory_end']-$data_values['memory_start']);
                }
                echo '</div>'."\n";
            }
        }

        if (strpos($data_values['data'], ';') !== false) {
            // prevent sql injection! example: SELECT * FROM table where username = 'john'; DROP TABLE table;' this can execute 2 queries. explode them and just get the first!
            $exp_data = explode(';', str_replace('; ', ';', $data_values['data']));
            $data_values['data'] = $exp_data[0];
        }

        // use try ... catch to prevent any error by EXPLAIN. Example: EXPLAIN SHOW CHARACTER SET; <-- this will throw errors!
        try {
            $sth = $dbh->prepare('EXPLAIN '.$data_values['data']);
            $sth->execute();
            if ($sth) {
                echo '<div class="rdprofiler-log-newrow">'."\n";
                echo '<div class="rdprofiler-log-db-explain">'."\n";
                if (isset($exp_data) && is_array($exp_data)) {
                    foreach ($exp_data as $key => $sqldata) {
                        if ($key != 0 && !empty($sqldata)) {
                            echo htmlspecialchars($sqldata, ENT_QUOTES).' cannot be explain due to it might be SQL injection!<br>'."\n";
                        }
                    }// endforeach;
                    unset($key, $sqldata);
                }
                $result = $sth->fetchAll();
                $sth->closeCursor();
                if ($result) {
                    foreach ($result as $row) {
                        if (is_array($row) || is_object($row)) {
                            foreach ($row as $key => $val) {
                                echo $key . ' = ' . $val;
                                if (end($result) != $val) {
                                    echo ', ';
                                }
                            }// endforeach;
                        }
                        echo '<br>'."\n";
                    }// endforeach;
                }
                unset($key, $result, $row, $val);
                echo '</div>'."\n";
                echo '</div>'."\n";
            }
            unset($sth);
        } catch (\Exception $e) {
            echo '<div class="rdprofiler-log-newrow">'."\n";
            echo '<div class="rdprofiler-log-db-explain">'."\n";
            echo '</div>'."\n";
            echo '</div>'."\n";
        }
        unset($exp_data);

        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);
    }// display


}

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

<?php


// db config
$configdb = [];
$configdb['host'] = 'localhost';
$configdb['dbname'] = '';
$configdb['username'] = '';
$configdb['password'] = '';

// require the class files. you may no need these if you install via composer.
require dirname(dirname(__DIR__)).'/Rundiz/Profiler/ProfilerBase.php';
require dirname(dirname(__DIR__)).'/Rundiz/Profiler/Profiler.php';

// begins profiler
$profiler = new \Rundiz\Profiler\Profiler();
$profiler->Console->registerLogSections(['Logs', 'Database']);
$profiler->Console->log('debug', 'Profiler initialized', __FILE__, __LINE__);

// connect to db.
$link = mysqli_connect($configdb['host'], $configdb['username'], $configdb['password'], $configdb['dbname']);
$profiler->Console->log('info', 'MySQLi connected. Here is the data.'."\n".print_r($link, true), __FILE__, __LINE__);
?>
<!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>Database</li>
        </ul>

        <h2>Test database</h2>
        <?php
        $time_start = microtime(true);
        $memory_start = memory_get_usage();

        $sql = 'SELECT * FROM `people` LIMIT 0, 20';
        $result = mysqli_query($link, $sql);
        $results = mysqli_fetch_all($result, MYSQLI_ASSOC);

        // profiling the db query.
        $profiler->Console->database($sql, $time_start, $memory_start);
        $profiler->Console->log('debug', 'MySQLi queried ('.$sql.').', __FILE__, __LINE__);
        unset($memory_start, $time_start);
        ?> 
        <p>Basic listing; Limits <?php echo $result->num_rows; ?> 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.
        mysqli_free_result($result);
        unset($result, $results, $sql);

        // profiler more db queries
        // set start time and memory.
        $time_start = microtime(true);
        $memory_start = memory_get_usage();
        // begins query.
        $sql = 'SELECT * FROM people';
        $result = mysqli_query($link, $sql);
        // record query profiler.
        $profiler->Console->database($sql, $time_start, $memory_start);
        $profiler->Console->log('debug', 'MySQLi queried ('.$sql.').', __FILE__, __LINE__);
        unset($memory_start, $time_start);

        // display profiler window.
        echo $profiler->display($link, 'displayProfilerDb');
        ?> 
    </body>
</html>
<?php
mysqli_close($link);
unset($configdb, $link, $profiler);


function displayProfilerDb(\Rundiz\Profiler\Profiler $profiler, $link, array $data_values)
{
    if (is_array($data_values)) {
        if (array_key_exists('time_start', $data_values) && array_key_exists('time_end', $data_values)) {
            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_end', $data_values) && array_key_exists('memory_start', $data_values) && is_int($data_values['memory_end']) && is_int($data_values['memory_start'])) {
            echo '<div class="rdprofiler-log-memory">';
            echo $profiler->getReadableFileSize($data_values['memory_end']-$data_values['memory_start']);
            echo '</div>'."\n";
        }
    }

    if (strpos($data_values['data'], ';') !== false) {
        // prevent sql injection! example: SELECT * FROM table where username = 'john'; DROP TABLE table;' this can execute 2 queries. explode them and just get the first!
        $exp_data = explode(';', str_replace('; ', ';', $data_values['data']));
        $data_values['data'] = $exp_data[0];
    }

    // use try ... catch to prevent any error by EXPLAIN. Example: EXPLAIN SHOW CHARACTER SET; <-- this will throw errors!
    try {
        $result = mysqli_query($link, 'EXPLAIN '.$data_values['data']);
        if ($result) {
            echo '<div class="rdprofiler-log-newrow">'."\n";
            echo '<div class="rdprofiler-log-db-explain">'."\n";
            if (isset($exp_data) && is_array($exp_data)) {
                foreach ($exp_data as $key => $sqldata) {
                    if ($key != 0 && !empty($sqldata)) {
                        echo htmlspecialchars($sqldata, ENT_QUOTES).' cannot be explain due to it might be SQL injection!<br>'."\n";
                    }
                }// endforeach;
                unset($key, $sqldata);
            }
            $results = mysqli_fetch_all($result, MYSQLI_ASSOC);
            if ($results) {
                foreach ($results as $row) {
                    if (is_array($row) || is_object($row)) {
                        foreach ($row as $key => $val) {
                            echo $key . ' = ' . $val;
                            if (end($results) != $val) {
                                echo ', ';
                            }
                        }// endforeach;
                    }
                    echo '<br>'."\n";
                }// endforeach;
            }
            unset($key, $results, $row, $val);
            echo '</div>'."\n";
            echo '</div>'."\n";
        }
        mysqli_free_result($result);
        unset($result);
    } catch (\Exception $ex) {
        echo '<div class="rdprofiler-log-newrow">'."\n";
        echo '<div class="rdprofiler-log-db-explain">'."\n";
        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($exp_data);
}// displayProfilerDb

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

okvee profiler screenshot

ใส่ความเห็น

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น ช่องข้อมูลจำเป็นถูกทำเครื่องหมาย *

คุณอาจใช้แท็กHTMLและแอททริบิวต์เหล่านี้: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>