Sunday, May 24, 2009

Добрый день!

Я хочу рассказать Вам о моем маленьком open-source проекте, который может показаться интересным людям, профилирующим PHP-приложения с помощью Xdebug. Как многим из Вас известно, в результате работы Xdebug'а получаются логи в формате cachegrind. Их, в свою очередь, можно анализировать с помощью старого доброго KCachegrind, можно разбирать посредством Webgrind, CachegrindVisualizer, и т.д. Но я решил пойти другим путем, и вот что у меня получилось...

Итак, встречаем xdebugtoolkit! Это набор простых утилиток, основная из которых - cg2dot - умеет преобразовывать файлы Xdebug'а в вид понятных раскрашенных деревьев вызовов в формате dot, для просмотра которых рекомендую другую утилитку - xdot.

Одной из своих находок, еще со времен работы над cachegrindvisualizer, я считаю то, что мне удалось сделать максимально компактные графы вызовов, не выходя за рамки древовидности. Это проще всего показать сразу на примере. Такой код, несмотря на множественные переиспользования функций, после агрегации выглядит таким образом:

Как видно, агрегируются только вызовы с одинаковыми путями. Например, вызовы функции d с путями

root -> {main} -> a -> c -> d
агрегируются между собой, но не с вызовами по путям
root -> {main} -> b -> c -> d
Для сравнения, вот что бывает, когда граф перестает быть деревом.

Цвет каждой ноды состоит из двух компонент:

Желтый цвет - кол-во вызовов. Чисто жёлтым вызовом будет тот, который вызывали максимальное кол-во раз, но который при этом очень быстро отработал.

Сиреневый - собственное (без подвызовов) затраченное время. Чисто сиреневым будет единичный вызов с максмальным собственным временем.

Складывая желтый и сиреневый, получаем красный - такое бывает в случае большого кол-ва вызовов одной и той же функции, которые в сумме имеют очень большой self time.

Планы по дальнейшей разработке можно посмотреть в issues на google code.

PS: тому, что xdebugtoolkit написан на питоне, есть одно простое объяснение: проект начинался как реальная задача, на которой я хотел узнать питон получше. Производительность меня интересовала всегда, поэтому я решил совместить приятное с полезным.

Thursday, December 27, 2007

There were a conversation (in Russian) about how to generate files' TTH hashes compatible with DC. I couldn't find any ready-to-use solution in the Internet done in PHP. And taking into account some reefs in the implementation, I decided to make the class I'd written public. PS: use it at your own risk :)
<?php

/**
 * @author Alexey Kupershtokh <alexey.kupershtokh@gmail.com>
 * @url http://kupershtokh.blogspot.com/2007/12/on-phpclub.html
 */
class TTH {
  private static $BASE32_ALPHABET 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
  private static $tiger_hash null;
  private static $tiger_mhash null;

  /**
   * Generates DC-compatible TTH of a file.
   *
   * @param string $filename
   * @return string
   */
  public static function getTTH($filename) {
    $fp fopen($filename"rb");
    if($fp) {
      $i 1;
      $hashes = array();
      while(!feof($fp)) {
        $buf fread($fp1024);
        if ($buf || ($i == 1)) {
          $hashes[$i] = self::tiger("\0".$buf);
          $j 1;
          while($i % ($j 2) == 0) {
            $hashes[$i] = self::tiger("\1".$hashes[$i $j].$hashes[$i]);
            unset($hashes[$i $j]);
            $j round($j 2);
          }
          $i++;
        }
      }
      $k 1;
      while($i $k) {
        $k round($k 2);
      }
      for(; $i <= $k$i++) {
          $j 1;
          while($i % ($j 2) == 0) {
            if(isset($hashes[$i])) {
              $hashes[$i] = self::tiger("\1".$hashes[$i $j].$hashes[$i]);
            } elseif(isset($hashes[$i $j])) {
              $hashes[$i] = $hashes[$i $j];
            }
            unset($hashes[$i $j]);
            $j round($j 2);
          }
      }
      fclose($fp);

      return self::base32encode($hashes[$i-1]);
    }
  }

  /**
   * Generates a DC-compatible tiger hash (not TTH).
   * Automatically chooses between hash() and mhash().
   *
   * @param string $string
   * @return string
   */
  private static function tiger($string) {
    if (is_null(self::$tiger_hash)) {
       self::$tiger_hash function_exists("hash_algos") && in_array("tiger192,3"hash_algos());
    }
    if (self::$tiger_hash) {
      return self::tigerfix(hash("tiger192,3"$string1));
    }

    if (is_null(self::$tiger_mhash)) {
      self::$tiger_mhash function_exists("mhash");
    }
    if(self::$tiger_mhash) {
      return self::tigerfix(mhash(MHASH_TIGER$string));
    }

    trigger_error(E_USER_ERROR"Neither tiger hash function is available.");
  }

  /**
   * Repairs tiger hash for compatibility with DC.
   *
   * @url http://www.php.net/manual/en/ref.mhash.php#55737
   * @param string $binary_hash
   * @return string
   */ 
  private static function tigerfix($binary_hash) {
      $my_split str_split($binary_hash,8);
      $my_tiger ="";
      foreach($my_split as $key => $value) {
         $my_split[$key] = strrev($value);
         $my_tiger .= $my_split[$key];
      }
     return $my_tiger;
  }

  /**
   * Just a base32encode function :)
   *
   * @url http://www.php.net/manual/en/function.sha1-file.php#61741
   * @param string $input
   * @return string
   */
  private static function base32encode($input) {
    $output '';
    $position 0;
    $storedData 0;
    $storedBitCount 0;
    $index 0;
    while ($index strlen($input)) {
      $storedData <<= 8;
      $storedData += ord($input[$index]);
      $storedBitCount += 8;
      $index += 1;
      //take as much data as possible out of storedData
      while ($storedBitCount >= 5) {
        $storedBitCount -= 5;
        $output .= self::$BASE32_ALPHABET[$storedData >> $storedBitCount];
        $storedData &= ((<< $storedBitCount) - 1);
      }
    } //while
    //deal with leftover data
    if ($storedBitCount 0) {
      $storedData <<= (5-$storedBitCount);
      $output .= self::$BASE32_ALPHABET[$storedData];
    }
    return $output;
  }
}

?>