PageRenderTime 50ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/trace2table.php

https://github.com/hipe/php-analysis
PHP | 369 lines | 307 code | 28 blank | 34 comment | 7 complexity | a3c5ffae7fcc7e6c73c0a4507a770d30 MD5 | raw file
  1. <?php
  2. /*
  3. this is a standalone file to be run from the commandline
  4. that imports a tab- delimited xdebug trace file into the database,
  5. creating a table if necessary.
  6. This might be useful if you want to run database-like queries against the output of a machine-readable xdebug trace.
  7. It is basically a wrapper around the mysql LOAD DATA INFILE command with
  8. smarts for creating the table and erasing the data
  9. The table it creates will match the "structure" of the csv file.
  10. Note:
  11. this uses "load data infile local" for now, that is the tab file must be
  12. on the same server as the database *client* (not server). this could be changed.
  13. */
  14. /*
  15. # AWESOME QUERY DELTA SUM
  16. select t1.function, sum( t2.memory - t1.memory ) as deltaSum, count( t1.function) from _trace as t1
  17. join _trace as t2 on t1.function_num = t2.function_num and t2.is_exit = 1
  18. where t1.is_exit = 0
  19. group by ( t1.function )
  20. order by deltaSum desc
  21. */
  22. error_reporting( E_NOTICE | E_ALL );
  23. // ** clasess **
  24. class StdLogger {
  25. function out( $msg ){
  26. CliCommon::stdout( $msg );
  27. }
  28. function err( $msg ){
  29. CliCommon::stderr( $msg );
  30. }
  31. }
  32. class QueryException extends Exception {}
  33. /*
  34. the bulk of this is logic to build the structure of the table,
  35. and a wrapper around "load data infile"
  36. */
  37. class TableFromCsvBuilderPopulator{
  38. protected $createPrimaryKey = false;
  39. public function __construct( $args ){
  40. $this->logger = $args['logger'];
  41. $this->separator = $args['separator'];
  42. $this->csv_filename = $args['csv_filename'];
  43. $this->username = $args['connection_parameters']['username'];
  44. $this->password = $args['connection_parameters']['password'];
  45. $this->database = $args['connection_parameters']['database'];
  46. $this->table_name = $args['table_name'];
  47. $this->fp = null;
  48. $this->out_sql_filepath = "temp.".$this->table_name.".sql";
  49. $this->primaryKeyName = '_id'; // something not in the tab file
  50. }
  51. public function setDoPrimaryKey( $x ){
  52. $this->createPrimaryKey = true;
  53. }
  54. public function get_table_name(){
  55. return $this->table_name;
  56. }
  57. public function table_exists(){
  58. $q = "show tables where Tables_in_".$this->database." = '$this->table_name'";
  59. $rs = mysql_query( $q );
  60. return (bool) mysql_num_rows( $rs );
  61. }
  62. public function get_numrows_in_table(){
  63. $q = "select count(*) from `$this->table_name`";
  64. $rs = mysql_query( $q );
  65. $row = mysql_fetch_row( $rs );
  66. return $row[0];
  67. }
  68. public function create_table() {
  69. $this->run_sql("
  70. create table $this->table_name (
  71. level integer not null,
  72. function_num integer not null,
  73. is_exit integer (1) not null,
  74. time float not null,
  75. memory integer not null,
  76. function varchar(255),
  77. is_user_defined integer (1) not null,
  78. included_fn varchar(255),
  79. filename varchar(255),
  80. line_no integer
  81. )
  82. ");
  83. $this->run_sql( "alter table $this->table_name add index ( function_num ) " );
  84. $this->logger->out( "created table $this->table_name.\n" );
  85. }
  86. public function drop_table(){
  87. $q = "drop table `".$this->table_name."`";
  88. $result = $this->run_sql( $q );
  89. $this->logger->out( "dropped table $this->table_name.\n" );
  90. }
  91. public function delete_all_from_table() {
  92. $q = "delete from `".$this->table_name."`";
  93. $this->run_sql( $q );
  94. $num = mysql_affected_rows();
  95. $this->logger->out( "deleted $num rows from $this->table_name.\n" );
  96. if ($this->createPrimaryKey) {
  97. $this->run_sql( "alter table `$this->table_name` drop column
  98. $this->primaryKeyName
  99. ");
  100. $this->logger->out( "dropped $this->primaryKeyName column from $this->table_name.\n" );
  101. }
  102. }
  103. public function populate_table() {
  104. $this->run_sql( $this->get_load_data_query() );
  105. $this->pkCheck();
  106. }
  107. public function try_populate_table_workaround() {
  108. $fn = "temp.load_data_sql_statement.sql";
  109. if (false === file_put_contents($fn, $this->get_load_data_query())){
  110. $this->fatal( "couldn't open tempfile: $fn" );
  111. }
  112. $passwordArg = (''===$this->password) ? '' : (" -p=".$this->password);
  113. $command = "mysql ".$this->database." --local_infile=1 -u ".$this->username.$passwordArg." < $fn ;$this->password";
  114. $results = shell_exec( $command );
  115. if (NULL!==$results){ $this->fatal( "expected NULL had: '$results'"); }
  116. // we might want to keep the below file around for debugging purposes
  117. if (!unlink($fn)){ $this->fatal("couldn't erase file: $fn"); }
  118. $this->pkCheck();
  119. }
  120. private function pkCheck(){
  121. if (!$this->createPrimaryKey) return;
  122. $this->run_sql( "alter table `$this->table_name`
  123. add column`$this->primaryKeyName` int(11) not null auto_increment primary key first");
  124. $this->logger->out( "added primary key column to table\n" );
  125. }
  126. // ---------------- Protected Methods ----------------------
  127. protected function get_load_data_query(){
  128. switch( $this->separator ){
  129. case "\t": $terminator = '\\t'; break;
  130. case ',' : $terminator = ','; break;
  131. default: $this->fatal( "we need to code for this terminator: \"$this->separator\"" );
  132. }
  133. return "
  134. load data low_priority local infile '$this->csv_filename'
  135. into table `$this->table_name`
  136. fields terminated by '$terminator' enclosed by '' escaped by '\\\\'
  137. lines terminated by '\\n'
  138. ignore 1 lines
  139. ";
  140. }
  141. protected function run_sql( $q ){
  142. $ret = mysql_query( $q );
  143. if (false===$ret){
  144. throw new QueryException(
  145. "something wrong with this query: ".var_export( $q,1 )."\n\n".
  146. "mysql returned the error: ".mysql_error()."\n"
  147. );
  148. }
  149. return $ret;
  150. }
  151. }
  152. /**
  153. * candidates for abstraction
  154. */
  155. class CliCommon {
  156. /*
  157. ask a user a question on the command-line and require a yes or no answer (or cancel.)
  158. return 'yes' | 'no' | 'cancel' loops until one of these is entered
  159. if default value is provided it will be used if the user just presses enter w/o entering anything.
  160. if default value is not provided it will loop.
  161. */
  162. public static function yes_no_cancel( $prompt, $defaultValue = null){
  163. $done = false;
  164. if (null!==$defaultValue) {
  165. $defaultMessage =" (default: $defaultValue)";
  166. }
  167. while (!$done){
  168. echo $prompt;
  169. if (strlen($prompt)&&"\n"!==$prompt[strlen($prompt)-1]){ echo "\n"; }
  170. echo "Please enter [y]es, [n]o, or [c]ancel".$defaultMessage.": ";
  171. $input = strtolower( trim( fgets( STDIN ) ) );
  172. if (''===$input && null!== $defaultValue) {
  173. $input = $defaultValue;
  174. }
  175. $done = true;
  176. switch( $input ){
  177. case 'y': case 'yes': $return = 'yes'; break;
  178. case 'n': case 'no': $return = 'no'; break;
  179. case 'c': case 'cancel': $return = 'cancel'; break;
  180. default:
  181. $done = false;
  182. }
  183. }
  184. echo "\n"; // 2 newlines after user input for readability (the first was when they hit enter).
  185. return $return;
  186. }
  187. public static function stdout( $str ) {
  188. fwrite( STDOUT, $str );
  189. }
  190. public static function stderr( $str ) {
  191. fwrite( STDERR, $str );
  192. }
  193. }
  194. /**
  195. * in a standalone manner, handle all the issues with command line proccessing
  196. */
  197. class Tab2TableCli {
  198. public function processArgs( $argv ){
  199. if (count($argv) < 2 || (!in_array($argv[1], array('import','example')))) {
  200. $this->fatal( $this->get_usage_message() );
  201. }
  202. array_shift( $argv );
  203. $verb = array_shift( $argv );
  204. $methName = 'do_'.str_replace( ' ','_', strtolower( $verb ) );
  205. $this->$methName( $argv );
  206. }
  207. // **** Protected Methods ****
  208. function get_usage_message(){
  209. return "Usage:\n".
  210. " php ".$GLOBALS['argv'][0].' import <parameters file> <input csv>'."\n".
  211. "\n".
  212. "to output an example parameters file:\n".
  213. " php ". $GLOBALS['argv'][0].' example <parameters file name>'."\n "
  214. ;
  215. }
  216. protected function do_example( $args ){
  217. if (1 !== count( $args ) ) {
  218. $this->fatal(
  219. "expecting exactly one argument for filename.\n".$this->get_usage_message()
  220. );
  221. }
  222. $fn = $args[0];
  223. if (file_exists( $fn )){
  224. $this->fatal( "example parameters file must not already exist \"$fn\"");
  225. }
  226. $s = <<<TO_HERE
  227. <?php
  228. return array(
  229. 'connection' => array(
  230. 'username' => 'root',
  231. 'password' => '',
  232. 'server' => 'localhost',
  233. 'database' => 'sf3_dev',
  234. ),
  235. 'table_name' => '_trace',
  236. );
  237. TO_HERE;
  238. file_put_contents( $fn, $s );
  239. $this->stdout( "wrote example parameters file to \"$fn\".\n" );
  240. }
  241. protected function do_import( $argv ){
  242. if (count($argv) != 2){
  243. $this->fatal( "needed exactly two arguments.\n". $this->get_usage_message() );
  244. }
  245. $args['data_file'] = $argv[0];
  246. $args['input_csv'] = $argv[1];
  247. foreach( array( 'data_file','input_csv') as $filename_name ) {
  248. if (!file_exists( $filename = $args[$filename_name])) {
  249. $this->fatal( "there is no $filename_name with the path \"$filename\"" );
  250. }
  251. }
  252. $data = require_once( $args['data_file'] );
  253. $tbl_name = $data['table_name'];
  254. $c = $data['connection'];
  255. if (! $cn = mysql_connect( $c['server'], $c['username'], $c['password'] ) ){
  256. $this->fatal( "can't connect with ".var_export( $c, 1));
  257. }
  258. if (!mysql_select_db( $c['database'] ) ) {
  259. $this->fatal( "can't select database: ".$c['database'] );
  260. }
  261. if (!preg_match('/^_/', $tbl_name)) {
  262. $this->fatal( "table name must start with underscore (\"_\") -- invalid table name \"$tbl_name\"" );
  263. }
  264. $builder = new TableFromCsvBuilderPopulator(array(
  265. 'logger' => new StdLogger(),
  266. 'separator' => "\t",
  267. 'csv_filename' => $args['input_csv'],
  268. 'connection_parameters' => $c,
  269. 'table_name' => $tbl_name
  270. ));
  271. $builder->setDoPrimaryKey( true );
  272. $doDropTable = false;
  273. $doCreateTable = false;
  274. if ($builder->table_exists()) {
  275. $choice = CliCommon::yes_no_cancel( "table \"$tbl_name\" exists. Should we recreate its structure? (say 'yes' if you think the struture has changed.)\n", "no" );
  276. if ('cancel'===$choice) {
  277. $this->quit();
  278. } elseif ( 'yes' === $choice ) {
  279. $doDropTable = true;
  280. $doCreateTable = true;
  281. }
  282. } else {
  283. $doCreateTable = true;
  284. }
  285. if ($doDropTable) { $builder->drop_table(); }
  286. if ($doCreateTable) { $builder->create_table(); }
  287. if ($num = $builder->get_numrows_in_table()){
  288. $choice = CliCommon::yes_no_cancel( "table ".$builder->get_table_name()." has $num rows of data in it. Is it ok to delete this data?", 'yes' );
  289. if ($choice != 'yes') { $this->quit(); }
  290. $builder->delete_all_from_table();
  291. }
  292. try {
  293. $builder->populate_table();
  294. } catch ( QueryException $e ) {
  295. $str = "mysql returned the error: The used command is not allowed with this MySQL version";
  296. if (false !== strstr( $e->getMessage(), $str)){
  297. $this->stdout( "$str\n" );
  298. $this->stdout( "it is expected to be because the mysql server wasn't started with --local-infile enabled. \n");
  299. $choice = CliCommon::yes_no_cancel( "Should we try the workaround, to exec LOAD DATA INFILE from the command line? ",'yes' );
  300. if ($choice !== 'yes') { $this->quit(); }
  301. $builder->try_populate_table_workaround();
  302. } else {
  303. throw $e;
  304. }
  305. }
  306. $this->stdout("done.\n");
  307. }
  308. function quit(){
  309. $this->stdout( "quitting.\n");
  310. exit();
  311. }
  312. // php != mixins
  313. function stdout( $str ){ CliCommon::stdout( $str ); }
  314. function stderr( $str ){ CliCommon::stderr( $str ); }
  315. function fatal( $msg ){
  316. $this->stdout( $msg."\n" );
  317. die();
  318. }
  319. }
  320. $cli = new Tab2TableCli();
  321. $cli->processArgs( $argv );