├── .gitignore ├── LICENSE ├── LICENSE-flamegraph ├── Makefile ├── README.md ├── doc ├── examples.md ├── images │ ├── pgbench_pl-1.png │ ├── tpcb-problem-fixed.png │ ├── tpcb-test1-fetch_abalance.png │ ├── tpcb-test1-upd_accounts.png │ ├── tpcb-test1.png │ └── tpcb-using-collect.png ├── installation.md ├── pgbench_pl-1.html ├── plprofiler_cmd_ref.md ├── tpcb-problem-fixed.html ├── tpcb-test1.html └── tpcb-using-collect.html ├── examples ├── pgbench_default.profile ├── pgbench_pl.collect.profile ├── pgbench_pl.interval.profile ├── pgbench_pl.profile ├── pgbench_pl.sql ├── prepdb.sh └── tpcb_queries.sql ├── plprofiler--4.1--4.2.sql ├── plprofiler--4.2.sql ├── plprofiler.c ├── plprofiler.conf.sample ├── plprofiler.control ├── plprofiler.h └── python-plprofiler ├── .gitignore ├── MANIFEST.in ├── README.md ├── plprofiler ├── __init__.py ├── lib │ └── FlameGraph │ │ ├── README │ │ ├── README-plprofiler │ │ └── flamegraph.pl ├── plprofiler.py ├── plprofiler_report.py ├── plprofiler_tool.py └── sql_split.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | .*.swp 4 | *.pyc 5 | *.bc 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The Artistic License Preamble 2 | 3 | The intent of this document is to state the conditions under which a Package may 4 | be copied, such that the Copyright Holder maintains some semblance of artistic 5 | control over the development of the package, while giving the users of the 6 | package the right to use and distribute the Package in a more-or-less customary 7 | fashion, plus the right to make reasonable modifications. 8 | 9 | Definitions: 10 | 11 | "Package" refers to the collection of files distributed by the Copyright Holder, 12 | and derivatives of that collection of files created through textual 13 | modification. "Standard Version" refers to such a Package if it has not been 14 | modified, or has been modified in accordance with the wishes of the Copyright 15 | Holder. "Copyright Holder" is whoever is named in the copyright or copyrights 16 | for the package. "You" is you, if you're thinking about copying or distributing 17 | this Package. "Reasonable copying fee" is whatever you can justify on the basis 18 | of media cost, duplication charges, time of people involved, and so on. (You 19 | will not be required to justify it to the Copyright Holder, but only to the 20 | computing community at large as a market that must bear the fee.) "Freely 21 | Available" means that no fee is charged for the item itself, though there may be 22 | fees involved in handling the item. It also means that recipients of the item 23 | may redistribute it under the same conditions they received it. 24 | 25 | 1. You may make and give away verbatim copies of the source form of the Standard 26 | Version of this Package without restriction, provided that you duplicate all of 27 | the original copyright notices and associated disclaimers. 28 | 29 | 2. You may apply bug fixes, portability fixes and other modifications derived 30 | from the Public Domain or from the Copyright Holder. A Package modified in such 31 | a way shall still be considered the Standard Version. 32 | 33 | 3. You may otherwise modify your copy of this Package in any way, provided that 34 | you insert a prominent notice in each changed file stating how and when you 35 | changed that file, and provided that you do at least ONE of the following: 36 | 37 | a) place your modifications in the Public Domain or otherwise make them Freely 38 | Available, such as by posting said modifications to Usenet or an equivalent 39 | medium, or placing the modifications on a major archive site such as ftp.uu.net, 40 | or by allowing the Copyright Holder to include your modifications in the 41 | Standard Version of the Package. 42 | 43 | b) use the modified Package only within your corporation or organization. 44 | 45 | c) rename any non-standard executables so the names do not conflict with 46 | standard executables, which must also be provided, and provide a separate manual 47 | page for each non-standard executable that clearly documents how it differs from 48 | the Standard Version. 49 | 50 | d) make other distribution arrangements with the Copyright Holder. 51 | 52 | 4. You may distribute the programs of this Package in object code or executable 53 | form, provided that you do at least ONE of the following: 54 | 55 | a) distribute a Standard Version of the executables and library files, together 56 | with instructions (in the manual page or equivalent) on where to get the 57 | Standard Version. 58 | 59 | b) accompany the distribution with the machine-readable source of the Package 60 | with your modifications. 61 | 62 | c) accompany any non-standard executables with their corresponding Standard 63 | Version executables, giving the non-standard executables non-standard names, and 64 | clearly documenting the differences in manual pages (or equivalent), together 65 | with instructions on where to get the Standard Version. 66 | 67 | d) make other distribution arrangements with the Copyright Holder. 68 | 69 | 5. You may charge a reasonable copying fee for any distribution of this Package. 70 | You may charge any fee you choose for support of this Package. You may not 71 | charge a fee for this Package itself. However, you may distribute this Package 72 | in aggregate with other (possibly commercial) programs as part of a larger 73 | (possibly commercial) software distribution provided that you do not advertise 74 | this Package as a product of your own. 75 | 76 | 6. The scripts and library files supplied as input to or produced as output from 77 | the programs of this Package do not automatically fall under the copyright of 78 | this Package, but belong to whomever generated them, and may be sold 79 | commercially, and may be aggregated with this Package. 80 | 81 | 7. C or perl subroutines supplied by you and linked into this Package shall not 82 | be considered part of this Package. 83 | 84 | 8. The name of the Copyright Holder may not be used to endorse or promote 85 | products derived from this software without specific prior written permission. 86 | 87 | 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED 88 | WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF 89 | MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 90 | 91 | The End 92 | -------------------------------------------------------------------------------- /LICENSE-flamegraph: -------------------------------------------------------------------------------- 1 | COMMON DEVELOPMENT AND DISTRIBUTION LICENSE Version 1.0 2 | 3 | 1. Definitions. 4 | 5 | 1.1. "Contributor" means each individual or entity that creates 6 | or contributes to the creation of Modifications. 7 | 8 | 1.2. "Contributor Version" means the combination of the Original 9 | Software, prior Modifications used by a Contributor (if any), 10 | and the Modifications made by that particular Contributor. 11 | 12 | 1.3. "Covered Software" means (a) the Original Software, or (b) 13 | Modifications, or (c) the combination of files containing 14 | Original Software with files containing Modifications, in 15 | each case including portions thereof. 16 | 17 | 1.4. "Executable" means the Covered Software in any form other 18 | than Source Code. 19 | 20 | 1.5. "Initial Developer" means the individual or entity that first 21 | makes Original Software available under this License. 22 | 23 | 1.6. "Larger Work" means a work which combines Covered Software or 24 | portions thereof with code not governed by the terms of this 25 | License. 26 | 27 | 1.7. "License" means this document. 28 | 29 | 1.8. "Licensable" means having the right to grant, to the maximum 30 | extent possible, whether at the time of the initial grant or 31 | subsequently acquired, any and all of the rights conveyed 32 | herein. 33 | 34 | 1.9. "Modifications" means the Source Code and Executable form of 35 | any of the following: 36 | 37 | A. Any file that results from an addition to, deletion from or 38 | modification of the contents of a file containing Original 39 | Software or previous Modifications; 40 | 41 | B. Any new file that contains any part of the Original 42 | Software or previous Modifications; or 43 | 44 | C. Any new file that is contributed or otherwise made 45 | available under the terms of this License. 46 | 47 | 1.10. "Original Software" means the Source Code and Executable 48 | form of computer software code that is originally released 49 | under this License. 50 | 51 | 1.11. "Patent Claims" means any patent claim(s), now owned or 52 | hereafter acquired, including without limitation, method, 53 | process, and apparatus claims, in any patent Licensable by 54 | grantor. 55 | 56 | 1.12. "Source Code" means (a) the common form of computer software 57 | code in which modifications are made and (b) associated 58 | documentation included in or with such code. 59 | 60 | 1.13. "You" (or "Your") means an individual or a legal entity 61 | exercising rights under, and complying with all of the terms 62 | of, this License. For legal entities, "You" includes any 63 | entity which controls, is controlled by, or is under common 64 | control with You. For purposes of this definition, 65 | "control" means (a) the power, direct or indirect, to cause 66 | the direction or management of such entity, whether by 67 | contract or otherwise, or (b) ownership of more than fifty 68 | percent (50%) of the outstanding shares or beneficial 69 | ownership of such entity. 70 | 71 | 2. License Grants. 72 | 73 | 2.1. The Initial Developer Grant. 74 | 75 | Conditioned upon Your compliance with Section 3.1 below and 76 | subject to third party intellectual property claims, the Initial 77 | Developer hereby grants You a world-wide, royalty-free, 78 | non-exclusive license: 79 | 80 | (a) under intellectual property rights (other than patent or 81 | trademark) Licensable by Initial Developer, to use, 82 | reproduce, modify, display, perform, sublicense and 83 | distribute the Original Software (or portions thereof), 84 | with or without Modifications, and/or as part of a Larger 85 | Work; and 86 | 87 | (b) under Patent Claims infringed by the making, using or 88 | selling of Original Software, to make, have made, use, 89 | practice, sell, and offer for sale, and/or otherwise 90 | dispose of the Original Software (or portions thereof). 91 | 92 | (c) The licenses granted in Sections 2.1(a) and (b) are 93 | effective on the date Initial Developer first distributes 94 | or otherwise makes the Original Software available to a 95 | third party under the terms of this License. 96 | 97 | (d) Notwithstanding Section 2.1(b) above, no patent license is 98 | granted: (1) for code that You delete from the Original 99 | Software, or (2) for infringements caused by: (i) the 100 | modification of the Original Software, or (ii) the 101 | combination of the Original Software with other software 102 | or devices. 103 | 104 | 2.2. Contributor Grant. 105 | 106 | Conditioned upon Your compliance with Section 3.1 below and 107 | subject to third party intellectual property claims, each 108 | Contributor hereby grants You a world-wide, royalty-free, 109 | non-exclusive license: 110 | 111 | (a) under intellectual property rights (other than patent or 112 | trademark) Licensable by Contributor to use, reproduce, 113 | modify, display, perform, sublicense and distribute the 114 | Modifications created by such Contributor (or portions 115 | thereof), either on an unmodified basis, with other 116 | Modifications, as Covered Software and/or as part of a 117 | Larger Work; and 118 | 119 | (b) under Patent Claims infringed by the making, using, or 120 | selling of Modifications made by that Contributor either 121 | alone and/or in combination with its Contributor Version 122 | (or portions of such combination), to make, use, sell, 123 | offer for sale, have made, and/or otherwise dispose of: 124 | (1) Modifications made by that Contributor (or portions 125 | thereof); and (2) the combination of Modifications made by 126 | that Contributor with its Contributor Version (or portions 127 | of such combination). 128 | 129 | (c) The licenses granted in Sections 2.2(a) and 2.2(b) are 130 | effective on the date Contributor first distributes or 131 | otherwise makes the Modifications available to a third 132 | party. 133 | 134 | (d) Notwithstanding Section 2.2(b) above, no patent license is 135 | granted: (1) for any code that Contributor has deleted 136 | from the Contributor Version; (2) for infringements caused 137 | by: (i) third party modifications of Contributor Version, 138 | or (ii) the combination of Modifications made by that 139 | Contributor with other software (except as part of the 140 | Contributor Version) or other devices; or (3) under Patent 141 | Claims infringed by Covered Software in the absence of 142 | Modifications made by that Contributor. 143 | 144 | 3. Distribution Obligations. 145 | 146 | 3.1. Availability of Source Code. 147 | 148 | Any Covered Software that You distribute or otherwise make 149 | available in Executable form must also be made available in Source 150 | Code form and that Source Code form must be distributed only under 151 | the terms of this License. You must include a copy of this 152 | License with every copy of the Source Code form of the Covered 153 | Software You distribute or otherwise make available. You must 154 | inform recipients of any such Covered Software in Executable form 155 | as to how they can obtain such Covered Software in Source Code 156 | form in a reasonable manner on or through a medium customarily 157 | used for software exchange. 158 | 159 | 3.2. Modifications. 160 | 161 | The Modifications that You create or to which You contribute are 162 | governed by the terms of this License. You represent that You 163 | believe Your Modifications are Your original creation(s) and/or 164 | You have sufficient rights to grant the rights conveyed by this 165 | License. 166 | 167 | 3.3. Required Notices. 168 | 169 | You must include a notice in each of Your Modifications that 170 | identifies You as the Contributor of the Modification. You may 171 | not remove or alter any copyright, patent or trademark notices 172 | contained within the Covered Software, or any notices of licensing 173 | or any descriptive text giving attribution to any Contributor or 174 | the Initial Developer. 175 | 176 | 3.4. Application of Additional Terms. 177 | 178 | You may not offer or impose any terms on any Covered Software in 179 | Source Code form that alters or restricts the applicable version 180 | of this License or the recipients' rights hereunder. You may 181 | choose to offer, and to charge a fee for, warranty, support, 182 | indemnity or liability obligations to one or more recipients of 183 | Covered Software. However, you may do so only on Your own behalf, 184 | and not on behalf of the Initial Developer or any Contributor. 185 | You must make it absolutely clear that any such warranty, support, 186 | indemnity or liability obligation is offered by You alone, and You 187 | hereby agree to indemnify the Initial Developer and every 188 | Contributor for any liability incurred by the Initial Developer or 189 | such Contributor as a result of warranty, support, indemnity or 190 | liability terms You offer. 191 | 192 | 3.5. Distribution of Executable Versions. 193 | 194 | You may distribute the Executable form of the Covered Software 195 | under the terms of this License or under the terms of a license of 196 | Your choice, which may contain terms different from this License, 197 | provided that You are in compliance with the terms of this License 198 | and that the license for the Executable form does not attempt to 199 | limit or alter the recipient's rights in the Source Code form from 200 | the rights set forth in this License. If You distribute the 201 | Covered Software in Executable form under a different license, You 202 | must make it absolutely clear that any terms which differ from 203 | this License are offered by You alone, not by the Initial 204 | Developer or Contributor. You hereby agree to indemnify the 205 | Initial Developer and every Contributor for any liability incurred 206 | by the Initial Developer or such Contributor as a result of any 207 | such terms You offer. 208 | 209 | 3.6. Larger Works. 210 | 211 | You may create a Larger Work by combining Covered Software with 212 | other code not governed by the terms of this License and 213 | distribute the Larger Work as a single product. In such a case, 214 | You must make sure the requirements of this License are fulfilled 215 | for the Covered Software. 216 | 217 | 4. Versions of the License. 218 | 219 | 4.1. New Versions. 220 | 221 | Sun Microsystems, Inc. is the initial license steward and may 222 | publish revised and/or new versions of this License from time to 223 | time. Each version will be given a distinguishing version number. 224 | Except as provided in Section 4.3, no one other than the license 225 | steward has the right to modify this License. 226 | 227 | 4.2. Effect of New Versions. 228 | 229 | You may always continue to use, distribute or otherwise make the 230 | Covered Software available under the terms of the version of the 231 | License under which You originally received the Covered Software. 232 | If the Initial Developer includes a notice in the Original 233 | Software prohibiting it from being distributed or otherwise made 234 | available under any subsequent version of the License, You must 235 | distribute and make the Covered Software available under the terms 236 | of the version of the License under which You originally received 237 | the Covered Software. Otherwise, You may also choose to use, 238 | distribute or otherwise make the Covered Software available under 239 | the terms of any subsequent version of the License published by 240 | the license steward. 241 | 242 | 4.3. Modified Versions. 243 | 244 | When You are an Initial Developer and You want to create a new 245 | license for Your Original Software, You may create and use a 246 | modified version of this License if You: (a) rename the license 247 | and remove any references to the name of the license steward 248 | (except to note that the license differs from this License); and 249 | (b) otherwise make it clear that the license contains terms which 250 | differ from this License. 251 | 252 | 5. DISCLAIMER OF WARRANTY. 253 | 254 | COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" 255 | BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, 256 | INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED 257 | SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR 258 | PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND 259 | PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY 260 | COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE 261 | INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY 262 | NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF 263 | WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF 264 | ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS 265 | DISCLAIMER. 266 | 267 | 6. TERMINATION. 268 | 269 | 6.1. This License and the rights granted hereunder will terminate 270 | automatically if You fail to comply with terms herein and fail to 271 | cure such breach within 30 days of becoming aware of the breach. 272 | Provisions which, by their nature, must remain in effect beyond 273 | the termination of this License shall survive. 274 | 275 | 6.2. If You assert a patent infringement claim (excluding 276 | declaratory judgment actions) against Initial Developer or a 277 | Contributor (the Initial Developer or Contributor against whom You 278 | assert such claim is referred to as "Participant") alleging that 279 | the Participant Software (meaning the Contributor Version where 280 | the Participant is a Contributor or the Original Software where 281 | the Participant is the Initial Developer) directly or indirectly 282 | infringes any patent, then any and all rights granted directly or 283 | indirectly to You by such Participant, the Initial Developer (if 284 | the Initial Developer is not the Participant) and all Contributors 285 | under Sections 2.1 and/or 2.2 of this License shall, upon 60 days 286 | notice from Participant terminate prospectively and automatically 287 | at the expiration of such 60 day notice period, unless if within 288 | such 60 day period You withdraw Your claim with respect to the 289 | Participant Software against such Participant either unilaterally 290 | or pursuant to a written agreement with Participant. 291 | 292 | 6.3. In the event of termination under Sections 6.1 or 6.2 above, 293 | all end user licenses that have been validly granted by You or any 294 | distributor hereunder prior to termination (excluding licenses 295 | granted to You by any distributor) shall survive termination. 296 | 297 | 7. LIMITATION OF LIABILITY. 298 | 299 | UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT 300 | (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE 301 | INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF 302 | COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE 303 | LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR 304 | CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT 305 | LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK 306 | STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER 307 | COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN 308 | INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF 309 | LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL 310 | INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT 311 | APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO 312 | NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR 313 | CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT 314 | APPLY TO YOU. 315 | 316 | 8. U.S. GOVERNMENT END USERS. 317 | 318 | The Covered Software is a "commercial item," as that term is 319 | defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial 320 | computer software" (as that term is defined at 48 321 | C.F.R. 252.227-7014(a)(1)) and "commercial computer software 322 | documentation" as such terms are used in 48 C.F.R. 12.212 323 | (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 324 | C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all 325 | U.S. Government End Users acquire Covered Software with only those 326 | rights set forth herein. This U.S. Government Rights clause is in 327 | lieu of, and supersedes, any other FAR, DFAR, or other clause or 328 | provision that addresses Government rights in computer software 329 | under this License. 330 | 331 | 9. MISCELLANEOUS. 332 | 333 | This License represents the complete agreement concerning subject 334 | matter hereof. If any provision of this License is held to be 335 | unenforceable, such provision shall be reformed only to the extent 336 | necessary to make it enforceable. This License shall be governed 337 | by the law of the jurisdiction specified in a notice contained 338 | within the Original Software (except to the extent applicable law, 339 | if any, provides otherwise), excluding such jurisdiction's 340 | conflict-of-law provisions. Any litigation relating to this 341 | License shall be subject to the jurisdiction of the courts located 342 | in the jurisdiction and venue specified in a notice contained 343 | within the Original Software, with the losing party responsible 344 | for costs, including, without limitation, court costs and 345 | reasonable attorneys' fees and expenses. The application of the 346 | United Nations Convention on Contracts for the International Sale 347 | of Goods is expressly excluded. Any law or regulation which 348 | provides that the language of a contract shall be construed 349 | against the drafter shall not apply to this License. You agree 350 | that You alone are responsible for compliance with the United 351 | States export administration regulations (and the export control 352 | laws and regulation of any other countries) when You use, 353 | distribute or otherwise make available any Covered Software. 354 | 355 | 10. RESPONSIBILITY FOR CLAIMS. 356 | 357 | As between Initial Developer and the Contributors, each party is 358 | responsible for claims and damages arising, directly or 359 | indirectly, out of its utilization of rights under this License 360 | and You agree to work with Initial Developer and Contributors to 361 | distribute such responsibility on an equitable basis. Nothing 362 | herein is intended or shall be deemed to constitute any admission 363 | of liability. 364 | 365 | -------------------------------------------------------------------- 366 | 367 | NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND 368 | DISTRIBUTION LICENSE (CDDL) 369 | 370 | For Covered Software in this distribution, this License shall 371 | be governed by the laws of the State of California (excluding 372 | conflict-of-law provisions). 373 | 374 | Any litigation relating to this License shall be subject to the 375 | jurisdiction of the Federal Courts of the Northern District of 376 | California and the state courts of the State of California, with 377 | venue lying in Santa Clara County, California. 378 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | MODULE_big = plprofiler 3 | OBJS = plprofiler.o 4 | 5 | EXTENSION = plprofiler 6 | DATA = plprofiler--4.1--4.2.sql \ 7 | plprofiler--4.2.sql 8 | 9 | ifdef USE_PGXS 10 | PG_CONFIG = pg_config 11 | PGXS := $(shell $(PG_CONFIG) --pgxs) 12 | include $(PGXS) 13 | else 14 | subdir = contrib/plprofiler 15 | top_builddir = ../.. 16 | include $(top_builddir)/src/Makefile.global 17 | include $(top_srcdir)/contrib/contrib-global.mk 18 | 19 | plprofiler.o: CFLAGS += -I$(top_builddir)/src/pl/plpgsql/src 20 | endif 21 | 22 | plprofiler.o: plprofiler.c plprofiler.h 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # plProfiler 2 | 3 | The **plprofiler** is an extension for the PostgreSQL database system to create performance profiles of PL/pgSQL functions and stored procedures. The included external Python class and command line utility can be used to easily control the extension, run arbitrary SQL commands (invoking PL/pgSQL functions), save and manage the resulting performance datasets and create HTML reports from them. 4 | 5 | 1. [Overview](#markdown-header-overview) 6 | 2. [Installation](doc/installation.md) 7 | 3. [Examples](doc/examples.md) 8 | * [The example test case](doc/examples.md#markdown-header-the-example-test-case) 9 | * [General command syntax](doc/examples.md#markdown-header-general-command-syntax) 10 | * [Executing SQL using the plprofiler utility](doc/examples.md#markdown-header-executing-sql-using-the-plprofiler-utility) 11 | * [Analyzing the first profile](doc/examples.md#markdown-header-analyzing-the-first-profile) 12 | * [Capturing profiling data by instrumenting the application](doc/examples.md#markdown-header-capturing-profiling-data-by-instrumenting-the-application) 13 | * [Collecting statistics at a timed interval](doc/examples.md#markdown-header-collecting-statistics-at-a-timed-interval) 14 | * [Collecting statistics via ALTER USER](doc/examples.md#markdown-header-collecting-statistics-via-alter-user) 15 | * [Profiling a live production system](doc/examples.md#markdown-header-profiling-a-live-production-system) 16 | * [Fixing the performance problem](doc/examples.md#markdown-header-fixing-the-performance-problem) 17 | 4. [plprofiler command reference](doc/plprofiler_cmd_ref.md) 18 | * [Command run](doc/plprofiler_cmd_ref.md#markdown-header-command-run) 19 | * [Command monitor](doc/plprofiler_cmd_ref.md#markdown-header-command-monitor) 20 | * [Command reset-data](doc/plprofiler_cmd_ref.md#markdown-header-command-reset-data) 21 | * [Command save](doc/plprofiler_cmd_ref.md#markdown-header-command-save) 22 | * [Command list](doc/plprofiler_cmd_ref.md#markdown-header-command-list) 23 | * [Command edit](doc/plprofiler_cmd_ref.md#markdown-header-command-edit) 24 | * [Command report](doc/plprofiler_cmd_ref.md#markdown-header-command-report) 25 | * [Command delete](doc/plprofiler_cmd_ref.md#markdown-header-command-delete) 26 | * [Command export](doc/plprofiler_cmd_ref.md#markdown-header-command-export) 27 | * [Command import](doc/plprofiler_cmd_ref.md#markdown-header-command-import) 28 | 29 | Overview 30 | -------- 31 | 32 | Finding performance problems within PL/pgSQL functions and stored procedures can be difficult, especially when the code is nested. This is because PL/pgSQL creates a cloak over whatever is happening inside. The only thing visible in system or extension views, such as `pg_stat_activity` or `pg_stat_statements` is the query, sent from the client. In the case of invoking a stored procedure, that is just the outermost stored procedure call. 33 | 34 | The **plprofiler** extension can be used to quickly identify the most time consuming functions and then drill down to find the individual statements within them. 35 | 36 | The output, generated by the **plprofiler**, is a self-contained HTML document. The document starts with a [FlameGraph](http://www.brendangregg.com/FlameGraphs/cpuflamegraphs.html) at the top, followed by details about functions in the profile. Unlike usual CPU FlameGraphs, the **plprofiler** FlameGraph is based on the actual Wall-Clock time, spent in the PL/pgSQL functions. By default, the top ten functions, based on their self_time (total_time - children_time), are detailed. This can be overridden by the user. 37 | 38 | Click on the screenshot below to see the actual, interactive report in your browser. 39 | 40 | [ ![ Example report ](doc/images/pgbench_pl-1.png) ](http://wi3ck.info/plprofiler/doc/pgbench_pl-1.html) 41 | [`doc/pgbench_pl-1.html`](http://wi3ck.info/plprofiler/doc/pgbench_pl-1.html) 42 | 43 | Please see the [Examples](doc/examples.md) for more details about this interactive report. 44 | 45 | Credits for the FlameGraph go to [Brendan Gregg](http://www.brendangregg.com/). His `flamegraph.pl` script is used by the **plprofiler** utility to generate these incredibly powerful, interactive SVGs. 46 | 47 | #####Major Change History 48 | * 2012 - Removed from PostgreSQL plDebugger Extension 49 | * 2015 - Resurrected as standalone plProfiler by OpenSCG 50 | * 2016 - Rewritten as v2 to use shared hash tables, have lower overhead 51 | * 2016 - v3 Major performance improvements, flame graph UI 52 | * 2019 - v3.5 Placed all extension objects under role plprofiler for easier grant 53 | -------------------------------------------------------------------------------- /doc/examples.md: -------------------------------------------------------------------------------- 1 | PL Profiler Examples 2 | ==================== 3 | 4 | In this tutorial style set of examples, I mostly want to demonstrate the different ways, **plprofiler** allows to capture profiling data. 5 | 6 | The examples are built on top of each other so it is best to at least once read over this document top to bottom. 7 | 8 | It is assumed that anyone, interested in profiling complex PL/pgSQL code, is familiar with performance testing in general and performance testing of a PostgreSQL database in particular. Therefore it is also also assumed that the reader has a basic understanding of the pgbench utility. 9 | 10 | The example test case 11 | --------------------- 12 | 13 | All examples in this documentation are based on a modified pgbench database. The modifications are: 14 | 15 | * The SQL queries, that make up the TPC-B style business transaction of pgbench, have been implemented in a set of PL/pgSQL functions. Each function essentially performs only one of the TPC-B queries. This is on purpose convoluted, since for the sake of demonstration we want a simple, yet nested example. The function definitions can be found in [`examples/pgbench_pl.sql`](../examples/pgbench_pl.sql). 16 | * A custom pgbench profile, found in [`examples/pgbench_pl.profile`](../examples/pgbench_pl.profile), is used with the -f option when invoking pgbench. 17 | * The table pgbench_accounts is modified. 18 | * The filler column is expanded and filled with 500 characters of data. 19 | * A new column, `category interger` is added in front of the aid and made part of the primary key. 20 | 21 | NOTE: The command syntax for pgbench custom profiles was changed in PostgreSQL 9.6. There are 9.6 specific profiles in the examples directory as well. 22 | 23 | The modifications to the pgbench_accounts table are based on a real world case, encountered in a customer database. This pgbench example case of course is greatly simplified. In the real world case the access to the table in question was in a nested function, 8 call levels deep, the table had several indexes to choose from and the schema contained a total of >500 PL/pgSQL functions with >100,000 lines of PL code. In other words the author was looking for a needle in what once was a haystack, but had been eaten by an elephant. 24 | 25 | Despite the simplification, the problem produced by these modifications simulates the original case surprisingly well. The TPC-B transaction accesses the pgbench_accounts table based on the aid column alone, so that is the only key part, available in the WHERE clause. However, since the table rows are now >500 bytes wide and the index is rather small, compared to the heap, the PostgreSQL query optimizer will still choose an index scan. This is the right choice, based on the available options, because a sequential scan would be worse. 26 | 27 | ``` 28 | pgbench_plprofiler=# explain select abalance from pgbench_accounts where aid = 1; 29 | QUERY PLAN 30 | -------------------------------------------------------------------------------------------------- 31 | Index Scan using pgbench_accounts_pkey on pgbench_accounts (cost=0.42..18484.43 rows=1 width=4) 32 | Index Cond: (aid = 1) 33 | ``` 34 | 35 | Since the first column of the index is not part of the WHERE clause and thus, the index condition, this results in a full scan of the entire index! Unfortunately that detail is nowhere visible except in this explain output. And then you will only notice it if you know the definition of that index. If we look at pg_stat_* tables after a benchmark run for example, they only tell us that all access to pgbench_accounts was done via index scans over the primary key and that all those scans returned a single row. One would normally think "nothing wrong here". 36 | 37 | On top of that, since the queries accessing the table will never show up in any statistics, we will never see that each of them takes 30ms already on a 10x pgbench scaling factor. Imagine what that turns into when we scale out. 38 | 39 | The full script to prepare the pgbench test database is found in [`examples/prepdb.sh`](../examples/prepdb.sh). 40 | 41 | To get a performance baseline, the median result of 5 times 5 minutes pgbench with 24 clients reports 136 TPS on an 8-core machine with 32GB of RAM and the entire database fitting into the 8GB of shared buffers (yeah, it is that bad). 42 | 43 | ``` 44 | (venv)[wieck@localhost examples]$ pgbench -n -c24 -j24 -T300 -f pgbench_pl.profile 45 | transaction type: Custom query 46 | scaling factor: 1 47 | query mode: simple 48 | number of clients: 24 49 | number of threads: 24 50 | duration: 300 s 51 | number of transactions actually processed: 40686 52 | latency average: 176.965 ms 53 | tps = 135.580039 (including connections establishing) 54 | tps = 135.589426 (excluding connections establishing) 55 | ``` 56 | 57 | Time to create a profile. 58 | 59 | General command syntax 60 | ---------------------- 61 | 62 | The general syntax of the plprofiler utility is 63 | 64 | `plprofiler COMMAND [OPTIONS]` 65 | 66 | Common for all commands are options, that control the database connection. These are 67 | 68 | Option | Description 69 | --------------------- | ------------------------ 70 | `-h, --host=HOST` | The host to connect to. 71 | `-p, --port=PORT` | Port number the postmaster is listening on. 72 | `-U, --user=USER` | The database user name. 73 | `-d, --dbname=DB` | The database name, conninfo string or URI. 74 | 75 | `plprofiler help [COMMAND]` will show you more details than are explained in the examples, provided in this document. 76 | 77 | In the examples below it is assumed that the environment variables `PGHOST`, `PGPORT`, `PGUSER` and `PGDATABASE` have all been set to point to the pgbench_plprofiler database, that was created using the [`examples/prepdb.sh`](../examples/prepdb.sh) script. The above connection parameters are left out to make the examples more readable. For security reasons, there is not way to specify a password on the command line. Please create the necessary `~/.pgpass` entry if your database requires password authentication. 78 | 79 | Executing SQL using the plprofiler utility 80 | ------------------------------------------ 81 | 82 | After having installed the **plprofiler** extension in the test database, the easiest way to generate a profile of PL/pgSQL functions is to run them using the plprofiler utility and let it create an HTML report directly from the local-data, collected in the backend. 83 | 84 | `plprofiler run --command "SELECT tpcb(1, 2, 3, -42)" --output tpcb-test1.html` 85 | 86 | Since not all information for the HTML report was actually specified on the command line, the utility will launch your `$EDITOR` with a config file after the SQL statement finished, so you have a chance to change some of the defaults before it renders the HTML. At the end this will create the report `tpcb-test1.html` in the current directory, which should look more or less like the one, presented in the [Overview](../README.md). 87 | 88 | One thing to keep in mind about this style of profiling is that there is a significant overhead in PL/pgSQL on the first call to a function within a database session (connection). The PL/pgSQL function call handler must parse the entire function definition and create a saved PL execution tree for it. Certain types of SQL statements will also be parsed and verified. For these reasons calling a truly trivial PL/pgSQL example like this can give very misleading results. 89 | 90 | To avoid this, the function should be called several times in a row. The file [`examples/tpcb_queries.sql`](../examples/tpcb_queries.sql) contains a set of 20 calls to the `tpcb()` function and can be executed as 91 | 92 | `plprofiler run --file tpcb_queries.sql --output tpcb-test1.html` 93 | 94 | Analyzing the first profile 95 | --------------------------- 96 | 97 | The report generated by the last `plprofiler` command (the one with the --file option used) should look roughly like this (I narrowed the SVG FlameGraph from the default width of 1200 pixels to 800 to play nicer with embedding into markdown on bitbucket) and I set the tabstop to 4, which is how the SQL file for the PL functions is formatted: 98 | 99 | [ ![tpcb-test1.hmtl](images/tpcb-test1.png) ](http://wi3ck.info/plprofiler/doc/tpcb-test1.html) 100 | [`doc/tpcb-test1.html`](http://wi3ck.info/plprofiler/doc/tpcb-test1.html) 101 | 102 | Go ahead and open the actual HTML version in a separate window or tab to be able to interact with it. 103 | 104 | What sticks out at the top of the FlameGraph are the two functions `tpcb_fetch_abalance()` and its caller, `tpcb_upd_accounts()`. When you hover over the FlameGraph entry for `tpcb_upd_accounts()` you will see that it actually accounted for over 99% of the total execution time, spent inside of PL/pgSQL functions. 105 | 106 | To examine this function closer we scroll down in the report to the details of `tpcb_upd_accounts()` and click on the **(show)** link, we can see the source code of the function and the execution time spent in every single line of it. 107 | 108 | ![Details of tpcb_upd_accounts](images/tpcb-test1-upd_accounts.png) 109 | 110 | Obviously there is a problem with accessing the pgbench_accounts table in that UPDATE statement. This function uses up 99% of our time and 50% of that is spent in a single row UPDATE statement? That cannot be right. 111 | 112 | Likewise if we examine the details for function `tpcb_fetch_abalance()`, we find that the same access path (single row SELECT via pgbench_accounts.aid) has the exact same performance problem. 113 | 114 | ![Details of tpcb_fetch_abalance](images/tpcb-test1-fetch_abalance.png) 115 | 116 | Of course, this all was an excercise in [Hunting an Elephant the Experienced Programmer's way](https://paws.kettering.edu/~jhuggins/humor/elephants.html). I deliberately placed an elephant in the middle of the room and found it. Not much of a surprise. It is what it is, the artificial reproduction of a real world problem encountered in the wild. You will have to take my word for it that it was almost as easy to find the problem in the real world case, this example is based on. 117 | 118 | We're not going to fix the actual problem (missing/wrong index) just yet, but explore alternative methods of invoking the **plprofiler** instead. This way we can compare all the different methods based on the same broken schema. 119 | 120 | Capturing profiling data by instrumenting the application 121 | --------------------------------------------------------- 122 | 123 | Sometimes it may be easier to add instrumentation calls to the application, than to extract stand alone queries, that can be run by the **plprofiler** via the --command or --file options. The way to do this is to add some **plprofiler** function calls at strategic places in the application code. In the case of pgbench, this *application code* is the custom profile [`pgbench_pl.collect.profile`](../examples/pgbench_pl.collect.profile). 124 | 125 | ``` 126 | \set nbranches :scale 127 | \set ntellers 10 * :scale 128 | \set naccounts 100000 * :scale 129 | \setrandom aid 1 :naccounts 130 | \setrandom bid 1 :nbranches 131 | \setrandom tid 1 :ntellers 132 | \setrandom delta -5000 5000 133 | SELECT pl_profiler_enable(true); 134 | SELECT tpcb(:aid, :bid, :tid, :delta); 135 | SELECT pl_profiler_collect_data(); 136 | SELECT pl_profiler_enable(false); 137 | ``` 138 | 139 | The function `pl_profiler_enable(true)` will cause the **plprofiler** extension to be loaded and start accumulating profiling data in the local-data hash tables. The function `pl_profiler_collect_data()` copies that local-data over to the shared hash tables and resets the local-data counters to zero. 140 | 141 | With this changed application code, we can run 142 | 143 | ``` 144 | plprofiler reset 145 | pgbench -n -c24 -j24 -T300 -fpgbench_pl.collect.profile 146 | ``` 147 | 148 | The `reset` command deletes all data from the shared hash tables. After pgbench has finished, we use the shared-data (the data, that has been copied by the `pl_profiler_collect_data()` function into the shared hash tables to generate a report. 149 | 150 | `plprofiler report --from-shared --name "tpcb-using-collect" --output "tpcb-using-collect.html"` 151 | 152 | [ ![tpcb-using-collect.hmtl](images/tpcb-using-collect.png) ](http://wi3ck.info/plprofiler/doc/tpcb-using-collect.html) 153 | [`doc/tpcb-using-collect.html`](http://wi3ck.info/plprofiler/doc/tpcb-using-collect.html) 154 | 155 | There seems to be only a subtle change in the profile. The functions for updating the pgbench_branches and pgbench_tellers tables, which are almost invisible in the first profile, now used 5.81% and 2.60% of the time. That may not look like much, but with the access to pgbench_accounts being as screwed up as it is, this is in fact huge. The difference was caused by concurrency (24 clients). 156 | 157 | Collecting statistics at a timed interval 158 | ----------------------------------------- 159 | 160 | Instead of collecting the local-data after each individual transaction, we can configure it to copy the local-data only every N seconds to the shared hash tables (and reset the local-data counters). The collecting happens at each transaction commit/rollback as well as when a PL/pgSQL function exits and the timer has elapsed. 161 | 162 | For this we use a slightly different pgbench custom profile, [`pgbench_pl.interval.profile`](../examples/pgbench_pl.interval.profile). 163 | 164 | ``` 165 | \set nbranches :scale 166 | \set ntellers 10 * :scale 167 | \set naccounts 100000 * :scale 168 | \setrandom aid 1 :naccounts 169 | \setrandom bid 1 :nbranches 170 | \setrandom tid 1 :ntellers 171 | \setrandom delta -5000 5000 172 | SET plprofiler.enabled TO true; 173 | SET plprofiler.collect_interval TO 10; 174 | SELECT tpcb(:aid, :bid, :tid, :delta); 175 | ``` 176 | 177 | I am not showing the resulting report for that because it is almost identical to the previous one. 178 | 179 | Collecting statistics via ALTER USER 180 | ------------------------------------ 181 | 182 | The above can also be done without changing the application code at all. Instead we can add the **plprofiler** to the `postgresql.conf` file in 183 | 184 | `shared_preload_libraries = 'plprofiler'` 185 | 186 | (requires PostgreSQL server restart) and then configure the application user as follows: 187 | 188 | ``` 189 | ALTER USER myuser SET plprofiler.enabled TO on; 190 | ALTER USER myuser SET plprofiler.collect_interval TO 10; 191 | ``` 192 | 193 | This has the exact same effect as the last example. It of course requires that the application reconnects after the `ALTER USER ...` statements to start collecting data, and it better reconnect once more when we are done profiling and did the corresponding `ALTER USER ... RESET ...` commands. So this is still not suitable for profiling a live production system since it is too disruptive. 194 | 195 | Profiling a live production system 196 | ---------------------------------- 197 | 198 | #### Debugging as well as profiling on a production system is a risky business and should be avoided if at all possible. 199 | 200 | Unfortunately sometimes it is not avoidable. For that reason, **plprofiler** has options designed to minimize its impact on performance. 201 | 202 | Like the previous example, the profiling method demonstrated below requires to have **plprofiler** pre-loaded from the `postgresql.conf` file. 203 | 204 | `shared_preload_libraries = 'plprofiler'` 205 | 206 | This by itself is not a problem. The **plprofiler** will be loaded and place all callback functions into the PL instrumentation hooks. The first thing all these functions do is to check if profiling is enabled. If nothing is enabled, this check amounts to evaluating an 207 | 208 | `if (!bool_var && int_var != ptr->int_var) return;` 209 | 210 | at the beginning of all the callback functions. One of the callback functions is called at every function enter/exit and at every PL statement start/end (only the statements, that actually have runtime functionality). In the great scheme of things, this overhead is negligible. 211 | 212 | With `shared_preload_libraries` configured (and the database server restarted to let that take effect) and the shared-data empty (run `plprofiler reset`) we launch `pgbench` in the background. After a while we get one of the pgbench backend PIDs by examining the system view `pg_stat_activity`. With that PID we run 213 | 214 | ``` 215 | plprofiler reset 216 | plprofiler monitor --pid --interval 10 --duration 300 217 | plprofiler report --from-shared --name tpcb-using-monitor --output tpcb-using-monitor.html 218 | ``` 219 | 220 | The `plprofiler monitor` command is using `ALTER SYSTEM ...` and `SELECT pg_reload_conf()` to enable profiling and turn it back off after the specified duration. This obviously will only work with a PostgreSQL database version 9.4 or newer. As with any database maintenance operations, this should only be done in a connection loss safe environment as losing the connection in the middle of the monitoring would leave those settings behind permanently. 221 | 222 | Leaving out the --pid option will cause ALL active backends to save their stats at the specified interval. 223 | 224 | Fixing the performance problem 225 | ------------------------------ 226 | 227 | In this final chapter of this tutorial we fix the artificially introduced performance problem as it was done in the real world case that stood model for it. We create the missing index. 228 | 229 | ``` 230 | CREATE INDEX pgbench_accounts_aid_idx ON pgbench_accounts (aid); 231 | ``` 232 | 233 | With that in place we use our last method of capturing profiling data once more to generate the last report for this tutorial. 234 | 235 | ``` 236 | plprofiler reset 237 | plprofiler monitor --pid --interval 10 --duration 300 238 | plprofiler report --from-shared --name tpcb-problem-fixed --output tpcb-problem-fixed.html 239 | ``` 240 | [ ![tpcb-problem-fixed.hmtl](images/tpcb-problem-fixed.png) ](http://wi3ck.info/plprofiler/doc/tpcb-problem-fixed.html) 241 | [`doc/tpcb-problem-fixed.html`](http://wi3ck.info/plprofiler/doc/tpcb-problem-fixed.html) 242 | 243 | The performance profile is now completely reversed. The access to pgbench_accounts is a small fraction (1.52% with 0.44% out of that accouting for fetching the new account balance) of the overall time spent. The access to pgbench_tellers and pgbench_branches completely dominates the picture. This is how a pgbench running inside of shared buffers is supposed to look like. Because the tellers and branches tables are so small, there is tremendous row level lock contention and constant bloat on them. 244 | 245 | The overall performance of pgbench went from the original 136 TPS to a whooping 246 | 247 | ``` 248 | transaction type: Custom query 249 | scaling factor: 1 250 | query mode: simple 251 | number of clients: 24 252 | number of threads: 24 253 | duration: 300 s 254 | number of transactions actually processed: 1086292 255 | latency average: 6.628 ms 256 | tps = 3620.469364 (including connections establishing) 257 | tps = 3620.869051 (excluding connections establishing) 258 | ``` 259 | 260 | This is a performance boost by factor 27 for one additional index. 261 | 262 | Not all performance problems are this easy to solve. But I hope the **plprofiler** will help you locating them quickly, so you have more time fixing them. 263 | -------------------------------------------------------------------------------- /doc/images/pgbench_pl-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigsql/plprofiler/1a8635b9bed831f31fae03098d86693ba549354c/doc/images/pgbench_pl-1.png -------------------------------------------------------------------------------- /doc/images/tpcb-problem-fixed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigsql/plprofiler/1a8635b9bed831f31fae03098d86693ba549354c/doc/images/tpcb-problem-fixed.png -------------------------------------------------------------------------------- /doc/images/tpcb-test1-fetch_abalance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigsql/plprofiler/1a8635b9bed831f31fae03098d86693ba549354c/doc/images/tpcb-test1-fetch_abalance.png -------------------------------------------------------------------------------- /doc/images/tpcb-test1-upd_accounts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigsql/plprofiler/1a8635b9bed831f31fae03098d86693ba549354c/doc/images/tpcb-test1-upd_accounts.png -------------------------------------------------------------------------------- /doc/images/tpcb-test1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigsql/plprofiler/1a8635b9bed831f31fae03098d86693ba549354c/doc/images/tpcb-test1.png -------------------------------------------------------------------------------- /doc/images/tpcb-using-collect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigsql/plprofiler/1a8635b9bed831f31fae03098d86693ba549354c/doc/images/tpcb-using-collect.png -------------------------------------------------------------------------------- /doc/installation.md: -------------------------------------------------------------------------------- 1 | Installing PL Profiler 2 | ====================== 3 | 4 | The **plprofiler** consists of two parts. 5 | * A backend side, which is provided by a PostgreSQL extension and 6 | * a client part, which is a Python package with a command line wrapper. 7 | 8 | Installing PL Profiler via the PGEDGE distribution 9 | -------------------------------------------------- 10 | 11 | In the [PGEDGE](https://github.com/pgedge/pgedge/) distribution both parts of the **plprofiler** are installed with the command 12 | 13 | ``` 14 | ./pgedge install plprofiler-pg14 15 | ``` 16 | 17 | Installing PL Profiler via PGDG RPMs 18 | ------------------------------------ 19 | 20 | If you installed PostgreSQL via the [PGDG yum repository](https://yum.postgresql.org/) it is highly recommended to install the **plprofiler** from the same. There are two packages and a meta-package pulling in both: 21 | * `plprofiler_XX-server` - the backend PostgreSQL extension 22 | * `plprofiler_XX-client` - the Python package with commandline wrapper 23 | * `plprofiler_XX` - the meta-package installing both 24 | 25 | The XX in the package name is the major version of PostgreSQL. On a PostgreSQL server you would normally install via the meta-package. The separate `plprofiler_XX-server` package is provided for installations where one intentionally does not provide debugging or testing utilities. 26 | 27 | The `plprofiler_XX-client` package is intended for developer workstations that do not have the PostgreSQL server itself installed. 28 | 29 | Installing the PL Profiler client via pip 30 | ----------------------------------------- 31 | 32 | In environments where users cannot install RPM packages, the `plprofiler-client` can be installed via Python's `pip` utility. It is recommended to use [Python Virtual Environments](https://docs.python.org/3/library/venv.html) in this case. 33 | 34 | ``` 35 | cd 36 | virtualenv --system-site-packages plprofiler-venv 37 | source ~/plprofiler-venv/bin/activate 38 | pip install plprofiler-client psycopg-binary 39 | ``` 40 | 41 | The `psycopg-binary` dependency needs to be specified manually since adding it to the `install_requires` list of setup.py would create an unsatisfied runtime dependency when installing `plprofiler-client` from RPM. 42 | 43 | Building PL Profiler from Source 44 | -------------------------------- 45 | 46 | **plprofiler** is implemented as a `PGXS` extension. This means it can be built either from within the PostgreSQL source tree, or outside of it when just the PostgreSQL devel RPM package is installed and `$PATH` includes the location of the correct `pg_config` utility. 47 | 48 | To install **plprofiler** from within a PostgreSQL source tree, the [**plprofiler** git repository](https://github.com/bigsql/plprofiler.git) needs to be cloned into the `contrib` directory. The following commands then install the backend extension: 49 | 50 | ``` 51 | cd contrib/plprofiler 52 | make install 53 | ``` 54 | 55 | If **plprofiler** was cloned outside of the contrib directory, use the following commands: 56 | 57 | ``` 58 | cd plprofiler 59 | USE_PGXS=1 make install 60 | ``` 61 | 62 | The `plprofiler-client` part in both cases is then installed via `setup.py`. It is recommended to use a [Python Virtual Environments](https://docs.python.org/3/library/venv.html) for this. 63 | 64 | ``` 65 | cd python-plprofiler 66 | python ./setup.py install 67 | ``` 68 | 69 | The client requires the psycopg database connector. Since the **plprofiler** client was installed via `pip` in this case, it is recommended to run 70 | 71 | ``` 72 | pip install psycopg-binary 73 | ``` 74 | -------------------------------------------------------------------------------- /doc/plprofiler_cmd_ref.md: -------------------------------------------------------------------------------- 1 | plprofiler command reference 2 | ============================ 3 | 4 | ``` 5 | usage: plprofiler COMMAND [OPTIONS] 6 | 7 | plprofiler is a command line tool to control the plprofiler extension 8 | for PostgreSQL. 9 | 10 | The input of this utility are the call and execution statistics, the 11 | plprofiler extension collects. The final output is an HTML report of 12 | the statistics gathered. There are several ways to collect the data, 13 | save the data permanently and even transport it from a production 14 | system to a lab system for offline analysis. 15 | 16 | Use 17 | 18 | plprofiler COMMAND --help 19 | 20 | for detailed information about one of the commands below. 21 | 22 | GENERAL OPTIONS: 23 | 24 | All commands implement the following command line options to specify 25 | the target database: 26 | 27 | -h, --host=HOST The host name of the database server. 28 | 29 | -p, --port=PORT The PostgreSQL port number. 30 | 31 | -U, --user=USER The PostgreSQL user name to connect as. 32 | 33 | -d, --dbname=DB The PostgreSQL database name or the DSN. 34 | plprofiler currently uses psycopg2 to connect 35 | to the target database. Since that is based 36 | on libpq, all the above parameters can also 37 | be specified in this option with the usual 38 | conninfo string or URI formats. 39 | 40 | --help Print the command specific help information 41 | and exit. 42 | 43 | TERMS: 44 | 45 | The following terms are used in the text below and the help output of 46 | individual commands: 47 | 48 | local-data By default the plprofiler extension collects run-time 49 | data in per-backend hashtables (in-memory). This data is 50 | only accessible in the current session and is lost when 51 | the session ends or the hash tables are explicitly reset. 52 | 53 | shared-data The plprofiler extension can copy the local-data 54 | into shared hashtables, to make the statistics available 55 | to other sessions. See the "monitor" command for 56 | details. This data still relies on the local database's 57 | system catalog to resolve Oid values into object 58 | definitions. 59 | 60 | saved-dataset The local-data as well as the shared-data can 61 | be turned into a named, saved dataset. These sets 62 | can be exported and imported onto other machines. 63 | The saved datasets are independent of the system 64 | catalog, so a report can be generated again later, 65 | even even on a different system. 66 | 67 | 68 | COMMANDS: 69 | 70 | run Runs one or more SQL statements with the plprofiler 71 | extension enabled and creates a saved-dataset and/or 72 | an HTML report from the local-data. 73 | 74 | monitor Monitors a running application for a requested time 75 | and creates a saved-dataset and/or an HTML report from 76 | the resulting shared-data. 77 | 78 | reset Deletes the data from shared hash tables. 79 | 80 | save Saves the current shared-data as a saved-dataset. 81 | 82 | list Lists the available saved-datasets. 83 | 84 | edit Edits the metadata of one saved-dataset. The metadata 85 | is used in the generation of the HTML reports. 86 | 87 | report Generates an HTML report from either a saved-dataset 88 | or the shared-data. 89 | 90 | delete Deletes a saved-dataset. 91 | 92 | export Exports one or all saved-datasets into a JSON file. 93 | 94 | import Imports the saved-datasets from a JSON file, created 95 | with the export command. 96 | ``` 97 | 98 | Command run 99 | ----------- 100 | 101 | ``` 102 | usage: plprofiler run [OPTIONS] 103 | 104 | Runs one or more SQL commands (hopefully invoking one or more PL/pgSQL 105 | functions and/or triggers), then turns the local-data into an HTML 106 | report and/or a saved-dataset. 107 | 108 | OPTIONS: 109 | 110 | --name=NAME The name of the data set to use in the HTML report or 111 | saved-dataset. 112 | 113 | --title=TITLE Ditto. 114 | 115 | --desc=DESC Ditto. 116 | 117 | -c, --command=CMD The SQL string to execute. Can be multiple SQL 118 | commands, separated by semicolon. 119 | 120 | -f, --file=FILE Read SQL commands to execute from FILE. 121 | 122 | --save Create a saved-dataset. 123 | 124 | --force Overwrite an existing saved-dataset of the same NAME. 125 | 126 | --output=FILE Save an HTML report in FILE. 127 | 128 | --top=N Include up to N function detail descriptions in the 129 | report (default=10). 130 | 131 | 132 | ``` 133 | 134 | Command monitor 135 | --------------- 136 | 137 | ``` 138 | usage: plprofiler monitor [OPTIONS] 139 | 140 | Turns profile data capturing and periodic saving on for either all 141 | database backends, or a single one (specified by PID), waits for a 142 | specified amount of time, then turns it back off. If during that 143 | time the application (or specific backend) is executing queries, that 144 | invoke PL/pgSQL functions, profile statistics will be collected into 145 | shared-data at the specified interval as well as every transaction 146 | end (commit or rollback). 147 | 148 | The resulting saved-data can be used with the "save" and "report" 149 | commands and cleared with "reset". 150 | 151 | NOTES: 152 | 153 | The change in configuration options will become visible to running 154 | backends when they go through the PostgreSQL TCOP loop. That is, when 155 | they receive the next "client" command, like a query or prepared 156 | statement execution request. They will not start/stop collecting 157 | data while they are in the middle of a long-running query. 158 | 159 | REQUIREMENTS: 160 | 161 | This command uses PostgreSQL features, that are only available in 162 | version 9.4 and higher. 163 | 164 | The plprofiler extension must be loaded via the configuration option 165 | "shared_preload_libraries" in the postgresql.conf file. 166 | 167 | OPTIONS: 168 | 169 | --pid=PID The PID of the backend, to monitor. If not given, the 170 | entire PostgreSQL instance will be suspect to monitoring. 171 | 172 | --interval=SEC Interval in seconds at which the monitored backend(s) 173 | will copy the local-data to shared-data and then 174 | reset their local-data. 175 | 176 | --duration=SEC Duration of the monitoring run in seconds. 177 | 178 | 179 | ``` 180 | 181 | Command reset 182 | ------------- 183 | 184 | ``` 185 | usage: plprofiler reset 186 | 187 | Deletes all data from the shared hashtables. This affects all databases 188 | in the cluster. 189 | 190 | This does NOT destroy any of the saved-datasets. 191 | 192 | 193 | ``` 194 | Command save 195 | ------------ 196 | 197 | ``` 198 | usage: plprofiler save [OPTIONS] 199 | 200 | The save command is used to create a saved-dataset from shared-data. 201 | Saved datasets are independent from the system catalog, since all their 202 | Oid based information has been resolved into textual object descriptions. 203 | Their reports can be recreated later or even on another system (after 204 | transport via export/import). 205 | 206 | OPTIONS: 207 | 208 | --name=NAME The name of the saved-dataset. Must be unique. 209 | 210 | --title=TITLE The title used by the report command in the 211 | tag of the generated HTML output. 212 | 213 | --desc=DESC An HTML formatted paragraph (or more) that describes 214 | the profiling report. 215 | 216 | --force Overwite an existing saved-dataset with the same NAME. 217 | 218 | NOTES: 219 | 220 | If the options for TITLE and DESC are not specified on the command line, 221 | the save command will launch an editor, allowing to edit the default 222 | report configuration. This metadata can later be changed with the 223 | "edit" command. 224 | ``` 225 | 226 | Command list 227 | ------------ 228 | 229 | ``` 230 | usage: plprofiler list 231 | 232 | Lists the available saved-datasets together with their TITLE. 233 | ``` 234 | 235 | Command edit 236 | ------------ 237 | 238 | ``` 239 | usage: plprofiler edit [OPTIONS] 240 | 241 | Launches an editor with the metadata of the specified saved-dataset. 242 | This allows to change not only the metadata itself, but also the 243 | NAME of the saved-dataaset. 244 | 245 | OPTIONS: 246 | 247 | --name=NAME The name of the saved-dataset to edit. 248 | ``` 249 | 250 | Command report 251 | -------------- 252 | 253 | ``` 254 | usage: plprofiler report [OPTIONS] 255 | 256 | Create an HTML report from either shared-data or a saved-dataset. 257 | 258 | OPTIONS: 259 | 260 | --from-shared Use the shared-data rather than a saved-dataset. 261 | 262 | --name=NAME The name of the saved-dataset to load or the NAME 263 | to use with --from-shared. 264 | 265 | --title=TITLE Override the TITLE found in the saved-dataset's 266 | metadata, or the TITLE to use with --from-shared. 267 | 268 | --desc=DESC Override the DESC found in the saved-dataset's 269 | metadata, or the DESC to use with --from-shared. 270 | 271 | --output=FILE Destination for the HTML report (default=stdout). 272 | 273 | --top=N Include up to N function detail descriptions in the 274 | report (default=10). 275 | ``` 276 | 277 | Command delete 278 | -------------- 279 | 280 | ``` 281 | usage: plprofiler delete [OPTIONS] 282 | 283 | Delete the named saved-dataset. 284 | 285 | OPTIONS: 286 | 287 | --name=NAME The name of the saved-dataset to delete. 288 | ``` 289 | 290 | Command export 291 | -------------- 292 | 293 | ``` 294 | usage: plprofiler export [OPTIONS] 295 | 296 | Export the shared-data or one or more saved-datasets as a JSON 297 | document. 298 | 299 | OPTIONS: 300 | 301 | --all Export all saved-datasets. 302 | 303 | --from-shared Export the shared-data instead of a saved-dataset. 304 | 305 | --name=NAME The NAME of the dataset to save. 306 | 307 | --title=TITLE The TITLE of the dataset in the export. 308 | 309 | --desc=DESC The DESC of the dataset in the export. 310 | 311 | --edit Launches the config editor for each dataset, included 312 | in the export. 313 | 314 | --output=FILE Save the JSON export data in FILE (default=stdout). 315 | ``` 316 | 317 | Command import 318 | -------------- 319 | 320 | ``` 321 | usage: plprofiler import [OPTIONS] 322 | 323 | Imports one or more datasets from an export file. 324 | 325 | OPTIONS: 326 | 327 | -f, --file=FILE Read the profile data from FILE. This should be the 328 | output of a previous "export" command. 329 | 330 | --edit Edit each dataset's metadata before storing it as 331 | a saved-dataset. 332 | 333 | --force Overwrite any existing saved-datasets with the same 334 | NAMEs, as they appear in the input file (or after 335 | editing). 336 | ``` 337 | -------------------------------------------------------------------------------- /doc/tpcb-problem-fixed.html: -------------------------------------------------------------------------------- 1 | <html> 2 | <head> 3 | <title>PL Profiler Report for tpcb-problem-fixed 4 | 5 | 74 | 75 | 76 | 117 | 118 | 119 | 120 |

PL Profiler Report for tpcb-problem-fixed

121 |

PL/pgSQL Call Graph

122 |
123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 135 | 299 | 300 | PL Profiler Report for tpcb-problem-fixed 301 | 302 | Reset Zoom 303 | 304 | public.tpcb_upd_branches() oid=66240 (81,747,512 samples, 28.58%) 305 | public.tpcb_upd_branches() oi.. 306 | 307 | 308 | public.tpcb_fetch_abalance() oid=66446 (1,248,733 samples, 0.44%) 309 | 310 | 311 | 312 | public.tpcb_upd_tellers() oid=66239 (195,487,118 samples, 68.35%) 313 | public.tpcb_upd_tellers() oid=66239 314 | 315 | 316 | public.tpcb_upd_accounts() oid=66238 (4,335,474 samples, 1.52%) 317 | 318 | 319 | 320 | all (286,011,539 samples, 100%) 321 | 322 | 323 | 324 | public.tpcb() oid=66237 (286,011,539 samples, 100.00%) 325 | public.tpcb() oid=66237 326 | 327 | 328 | public.tpcb_ins_history() oid=66241 (1,150,113 samples, 0.40%) 329 | 330 | 331 | 332 | 333 |
334 |

List of functions detailed below

335 | 343 |

All 6 functions (by self_time)

344 | 345 |

Function public.tpcb_upd_tellers() oid=66239 (show)

347 |

348 | self_time = 195,487,118 µs
349 | total_time = 195,487,118 µs 350 |

351 | 352 | 353 | 354 | 355 | 356 | 357 | 360 | 361 |
public.tpcb_upd_tellers (par_tid integer,
 par_delta integer)
358 |     RETURNS void 359 |
362 | 424 | 425 |

Function public.tpcb_upd_branches() oid=66240 (show)

427 |

428 | self_time = 81,747,512 µs
429 | total_time = 81,747,512 µs 430 |

431 | 432 | 433 | 434 | 435 | 436 | 437 | 440 | 441 |
public.tpcb_upd_branches (par_bid integer,
 par_delta integer)
438 |     RETURNS void 439 |
442 | 504 | 505 |

Function public.tpcb() oid=66237 (show)

507 |

508 | self_time = 3,291,322 µs
509 | total_time = 286,005,315 µs 510 |

511 | 512 | 513 | 514 | 515 | 516 | 517 | 520 | 521 |
public.tpcb (par_aid integer,
 par_bid integer,
 par_tid integer,
 par_delta integer)
518 |     RETURNS integer 519 |
522 | 619 | 620 |

Function public.tpcb_upd_accounts() oid=66238 (show)

622 |

623 | self_time = 3,086,741 µs
624 | total_time = 4,335,474 µs 625 |

626 | 627 | 628 | 629 | 630 | 631 | 632 | 635 | 636 |
public.tpcb_upd_accounts (par_aid integer,
 par_delta integer)
633 |     RETURNS integer 634 |
637 | 706 | 707 |

Function public.tpcb_fetch_abalance() oid=66446 (show)

709 |

710 | self_time = 1,248,733 µs
711 | total_time = 1,248,733 µs 712 |

713 | 714 | 715 | 716 | 717 | 718 | 719 | 722 | 723 |
public.tpcb_fetch_abalance (par_aid integer)
720 |     RETURNS integer 721 |
724 | 793 | 794 |

Function public.tpcb_ins_history() oid=66241 (show)

796 |

797 | self_time = 1,150,113 µs
798 | total_time = 1,150,113 µs 799 |

800 | 801 | 802 | 803 | 804 | 805 | 806 | 809 | 810 |
public.tpcb_ins_history (par_aid integer,
 par_tid integer,
 par_bid integer,
 par_delta integer)
807 |     RETURNS void 808 |
811 | 873 | 874 | 875 | -------------------------------------------------------------------------------- /doc/tpcb-test1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | PL Profiler Report for current 4 | 5 | 74 | 75 | 76 | 117 | 118 | 119 | 120 |

PL Profiler Report for current

121 |

122 | Example 1 for plprofiler documentation. 123 |

124 |

PL/pgSQL Call Graph

125 |
126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 138 | 302 | 303 | PL Profiler Report for current 304 | 305 | Reset Zoom 306 | 307 | public.tpcb() oid=66237 (1,073,876 samples, 100.00%) 308 | public.tpcb() oid=66237 309 | 310 | 311 | public.tpcb_fetch_abalance() oid=66446 (490,112 samples, 45.64%) 312 | public.tpcb_fetch_abalance() oid=66446 313 | 314 | 315 | all (1,073,876 samples, 100%) 316 | 317 | 318 | 319 | public.tpcb_upd_branches() oid=66240 (1,055 samples, 0.10%) 320 | 321 | 322 | 323 | public.tpcb_upd_accounts() oid=66238 (1,066,077 samples, 99.27%) 324 | public.tpcb_upd_accounts() oid=66238 325 | 326 | 327 | public.tpcb_ins_history() oid=66241 (1,034 samples, 0.10%) 328 | 329 | 330 | 331 | public.tpcb_upd_tellers() oid=66239 (2,878 samples, 0.27%) 332 | 333 | 334 | 335 | 336 |
337 |

List of functions detailed below

338 | 346 |

All 6 functions (by self_time)

347 | 348 |

Function public.tpcb_upd_accounts() oid=66238 (show)

350 |

351 | self_time = 575,965 µs
352 | total_time = 1,066,077 µs 353 |

354 | 355 | 356 | 357 | 358 | 359 | 360 | 363 | 364 |
public.tpcb_upd_accounts (par_aid integer,
 par_delta integer)
361 |     RETURNS integer 362 |
365 | 434 | 435 |

Function public.tpcb_fetch_abalance() oid=66446 (show)

437 |

438 | self_time = 490,112 µs
439 | total_time = 490,112 µs 440 |

441 | 442 | 443 | 444 | 445 | 446 | 447 | 450 | 451 |
public.tpcb_fetch_abalance (par_aid integer)
448 |     RETURNS integer 449 |
452 | 521 | 522 |

Function public.tpcb_upd_tellers() oid=66239 (show)

524 |

525 | self_time = 2,878 µs
526 | total_time = 2,878 µs 527 |

528 | 529 | 530 | 531 | 532 | 533 | 534 | 537 | 538 |
public.tpcb_upd_tellers (par_tid integer,
 par_delta integer)
535 |     RETURNS void 536 |
539 | 601 | 602 |

Function public.tpcb() oid=66237 (show)

604 |

605 | self_time = 2,832 µs
606 | total_time = 1,073,876 µs 607 |

608 | 609 | 610 | 611 | 612 | 613 | 614 | 617 | 618 |
public.tpcb (par_aid integer,
 par_bid integer,
 par_tid integer,
 par_delta integer)
615 |     RETURNS integer 616 |
619 | 716 | 717 |

Function public.tpcb_upd_branches() oid=66240 (show)

719 |

720 | self_time = 1,055 µs
721 | total_time = 1,055 µs 722 |

723 | 724 | 725 | 726 | 727 | 728 | 729 | 732 | 733 |
public.tpcb_upd_branches (par_bid integer,
 par_delta integer)
730 |     RETURNS void 731 |
734 | 796 | 797 |

Function public.tpcb_ins_history() oid=66241 (show)

799 |

800 | self_time = 1,034 µs
801 | total_time = 1,034 µs 802 |

803 | 804 | 805 | 806 | 807 | 808 | 809 | 812 | 813 |
public.tpcb_ins_history (par_aid integer,
 par_tid integer,
 par_bid integer,
 par_delta integer)
810 |     RETURNS void 811 |
814 | 876 | 877 | 878 | -------------------------------------------------------------------------------- /examples/pgbench_default.profile: -------------------------------------------------------------------------------- 1 | \set nbranches :scale 2 | \set ntellers 10 * :scale 3 | \set naccounts 100000 * :scale 4 | \setrandom aid 1 :naccounts 5 | \setrandom bid 1 :nbranches 6 | \setrandom tid 1 :ntellers 7 | \setrandom delta -5000 5000 8 | BEGIN; 9 | UPDATE pgbench_accounts SET abalance = abalance + :delta WHERE aid = :aid; 10 | SELECT abalance FROM pgbench_accounts WHERE aid = :aid; 11 | UPDATE pgbench_tellers SET tbalance = tbalance + :delta WHERE tid = :tid; 12 | UPDATE pgbench_branches SET bbalance = bbalance + :delta WHERE bid = :bid; 13 | INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (:tid, :bid, :aid, :delta, CURRENT_TIMESTAMP); 14 | END; 15 | -------------------------------------------------------------------------------- /examples/pgbench_pl.collect.profile: -------------------------------------------------------------------------------- 1 | \set nbranches :scale 2 | \set ntellers 10 * :scale 3 | \set naccounts 100000 * :scale 4 | \setrandom aid 1 :naccounts 5 | \setrandom bid 1 :nbranches 6 | \setrandom tid 1 :ntellers 7 | \setrandom delta -5000 5000 8 | SELECT pl_profiler_enable(true); 9 | SELECT tpcb(:aid, :bid, :tid, :delta); 10 | SELECT pl_profiler_collect_data(); 11 | SELECT pl_profiler_enable(false); 12 | -------------------------------------------------------------------------------- /examples/pgbench_pl.interval.profile: -------------------------------------------------------------------------------- 1 | \set nbranches :scale 2 | \set ntellers 10 * :scale 3 | \set naccounts 100000 * :scale 4 | \setrandom aid 1 :naccounts 5 | \setrandom bid 1 :nbranches 6 | \setrandom tid 1 :ntellers 7 | \setrandom delta -5000 5000 8 | SET plprofiler.enabled TO true; 9 | SET plprofiler.collect_interval TO 10; 10 | SELECT tpcb(:aid, :bid, :tid, :delta); 11 | -------------------------------------------------------------------------------- /examples/pgbench_pl.profile: -------------------------------------------------------------------------------- 1 | \set nbranches :scale 2 | \set ntellers 10 * :scale 3 | \set naccounts 100000 * :scale 4 | \setrandom aid 1 :naccounts 5 | \setrandom bid 1 :nbranches 6 | \setrandom tid 1 :ntellers 7 | \setrandom delta -5000 5000 8 | SELECT tpcb(:aid, :bid, :tid, :delta); 9 | -------------------------------------------------------------------------------- /examples/pgbench_pl.sql: -------------------------------------------------------------------------------- 1 | -- ---------------------------------------------------------------------- 2 | -- pgbench_pl.sql 3 | -- 4 | -- PL/pgSQL functions implementing the pgbench transaction. 5 | -- ---------------------------------------------------------------------- 6 | 7 | CREATE OR REPLACE FUNCTION 8 | tpcb(par_aid integer, par_bid integer, par_tid integer, par_delta integer) 9 | RETURNS integer AS 10 | $$ 11 | DECLARE 12 | var_abalance integer; 13 | BEGIN 14 | var_abalance = tpcb_upd_accounts(par_aid, par_delta); 15 | PERFORM tpcb_upd_tellers(par_tid, par_delta); 16 | PERFORM tpcb_upd_branches(par_bid, par_delta); 17 | PERFORM tpcb_ins_history(par_aid, par_tid, par_bid, par_delta); 18 | RETURN var_abalance; 19 | END; 20 | $$ 21 | LANGUAGE plpgsql; 22 | 23 | CREATE OR REPLACE FUNCTION 24 | tpcb_upd_accounts(par_aid integer, par_delta integer) 25 | RETURNS integer AS 26 | $$ 27 | BEGIN 28 | UPDATE pgbench_accounts SET abalance = abalance + par_delta 29 | WHERE aid = par_aid; 30 | RETURN tpcb_fetch_abalance(par_aid); 31 | END; 32 | $$ 33 | LANGUAGE plpgsql; 34 | 35 | CREATE OR REPLACE FUNCTION 36 | tpcb_fetch_abalance(par_aid integer) 37 | RETURNS integer AS 38 | $$ 39 | DECLARE 40 | var_abalance integer; 41 | BEGIN 42 | RETURN abalance FROM pgbench_accounts WHERE aid = par_aid; 43 | END; 44 | $$ 45 | LANGUAGE plpgsql; 46 | 47 | CREATE OR REPLACE FUNCTION 48 | tpcb_upd_tellers(par_tid integer, par_delta integer) 49 | RETURNS void AS 50 | $$ 51 | BEGIN 52 | UPDATE pgbench_tellers SET tbalance = tbalance + par_delta 53 | WHERE tid = par_tid; 54 | END; 55 | $$ 56 | LANGUAGE plpgsql; 57 | 58 | CREATE OR REPLACE FUNCTION 59 | tpcb_upd_branches(par_bid integer, par_delta integer) 60 | RETURNS void AS 61 | $$ 62 | BEGIN 63 | UPDATE pgbench_branches SET bbalance = bbalance + par_delta 64 | WHERE bid = par_bid; 65 | END; 66 | $$ 67 | LANGUAGE plpgsql; 68 | 69 | CREATE OR REPLACE FUNCTION 70 | tpcb_ins_history(par_aid integer, par_tid integer, par_bid integer, par_delta integer) 71 | RETURNS void AS 72 | $$ 73 | BEGIN 74 | INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) 75 | VALUES (par_tid, par_bid, par_aid, par_delta, CURRENT_TIMESTAMP); 76 | END; 77 | $$ 78 | LANGUAGE plpgsql; 79 | -------------------------------------------------------------------------------- /examples/prepdb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PGDATABASE=pgbench_plprofiler 4 | export PGDATABASE 5 | 6 | # ---- 7 | # Initialize the pgbench schema itself. 8 | # ---- 9 | pgbench -i -s 10 -F90 10 | 11 | # ---- 12 | # Create the stored procedures implementing the TPC-B transaction. 13 | # ---- 14 | psql 25 | #include 26 | #include 27 | #include "access/hash.h" 28 | #include "access/htup.h" 29 | #include "access/htup_details.h" 30 | #include "access/sysattr.h" 31 | #include "catalog/indexing.h" 32 | #include "catalog/namespace.h" 33 | #include "catalog/pg_extension.h" 34 | #include "catalog/pg_proc.h" 35 | #include "catalog/pg_type.h" 36 | #include "commands/extension.h" 37 | #include "funcapi.h" 38 | #include "mb/pg_wchar.h" 39 | #include "miscadmin.h" 40 | #include "pgstat.h" 41 | #include "plpgsql.h" 42 | #include "storage/ipc.h" 43 | #include "storage/spin.h" 44 | #include "utils/array.h" 45 | #include "utils/builtins.h" 46 | #include "utils/fmgroids.h" 47 | #include "utils/guc.h" 48 | #include "utils/lsyscache.h" 49 | #include "utils/memutils.h" 50 | #include "utils/palloc.h" 51 | #include "utils/syscache.h" 52 | 53 | PG_MODULE_MAGIC; 54 | 55 | #define PL_PROFILE_COLS 5 56 | #define PL_CALLGRAPH_COLS 5 57 | #define PL_FUNCS_SRC_COLS 3 58 | 59 | #define PL_MAX_STACK_DEPTH 200 60 | #define PL_MIN_FUNCTIONS 2000 61 | #define PL_MIN_CALLGRAPH 20000 62 | #define PL_MIN_LINES 200000 63 | 64 | 65 | #define PL_DBG_PRINT_STACK(_d, _s) do { \ 66 | int _i; \ 67 | printf("stack %s: db=%d bt=", _d, _s.db_oid); \ 68 | for (_i = 0; _i < PL_MAX_STACK_DEPTH && _s.stack[_i] != InvalidOid; _i++) { \ 69 | printf("%d,", _s.stack[_i]); \ 70 | } \ 71 | printf("\n"); \ 72 | } while(0); 73 | 74 | 75 | /********************************************************************** 76 | * Type and structure definitions 77 | **********************************************************************/ 78 | 79 | /* ---- 80 | * profilerLineInfo 81 | * 82 | * Per source code line stats kept in the profilerInfo below, which 83 | * is the data we put into the plugin_info of the executor state. 84 | * ---- 85 | */ 86 | typedef struct 87 | { 88 | int64 us_max; /* Slowest iteration of this stmt */ 89 | int64 us_total; /* Total time spent executing this stmt */ 90 | int64 exec_count; /* Number of times we executed this stmt */ 91 | instr_time start_time; /* Start time for this statement */ 92 | } profilerLineInfo; 93 | 94 | /* ---- 95 | * profilerInfo 96 | * 97 | * The information we keep in the estate->plugin_info. 98 | * ---- 99 | */ 100 | typedef struct 101 | { 102 | Oid fn_oid; /* The functions OID */ 103 | int line_count; /* Number of lines in this function */ 104 | profilerLineInfo *line_info; /* Performance counters for each line */ 105 | } profilerInfo; 106 | 107 | /* ---- 108 | * linestatsHashKey 109 | * 110 | * Hash key for the linestats hash tables (both local and shared). 111 | * ---- 112 | */ 113 | typedef struct 114 | { 115 | Oid db_oid; /* The OID of the database */ 116 | Oid fn_oid; /* The OID of the function */ 117 | } linestatsHashKey; 118 | 119 | /* ---- 120 | * linestatsLineInfo 121 | * 122 | * Per source code line statistics kept in the linestats hash table. 123 | * ---- 124 | */ 125 | typedef struct 126 | { 127 | int64 us_max; /* Maximum execution time of statement */ 128 | int64 us_total; /* Total sum of statement exec time */ 129 | int64 exec_count; /* Count of statement executions */ 130 | } linestatsLineInfo; 131 | 132 | /* ---- 133 | * linestatsEntry 134 | * 135 | * Per function data kept in the linestats hash table. 136 | * ---- 137 | */ 138 | typedef struct 139 | { 140 | linestatsHashKey key; /* hash key of entry */ 141 | slock_t mutex; /* Spin lock for updating counters */ 142 | int line_count; /* Number of lines in this function */ 143 | linestatsLineInfo *line_info; /* Performance counters for each line */ 144 | } linestatsEntry; 145 | 146 | typedef struct callGraphKey 147 | { 148 | Oid db_oid; 149 | Oid stack[PL_MAX_STACK_DEPTH]; 150 | } callGraphKey; 151 | 152 | typedef struct callGraphEntry 153 | { 154 | callGraphKey key; 155 | slock_t mutex; 156 | PgStat_Counter callCount; 157 | uint64 totalTime; 158 | uint64 childTime; 159 | uint64 selfTime; 160 | } callGraphEntry; 161 | 162 | typedef struct 163 | { 164 | LWLockId lock; 165 | bool profiler_enabled_global; 166 | int profiler_enabled_pid; 167 | int profiler_collect_interval; 168 | bool callgraph_overflow; 169 | bool functions_overflow; 170 | bool lines_overflow; 171 | int lines_used; 172 | linestatsLineInfo line_info[1]; 173 | } profilerSharedState; 174 | 175 | /********************************************************************** 176 | * Exported function prototypes 177 | **********************************************************************/ 178 | 179 | void _PG_init(void); 180 | void _PG_fini(void); 181 | 182 | Datum pl_profiler_get_stack(PG_FUNCTION_ARGS); 183 | Datum pl_profiler_linestats_local(PG_FUNCTION_ARGS); 184 | Datum pl_profiler_linestats_shared(PG_FUNCTION_ARGS); 185 | Datum pl_profiler_callgraph_local(PG_FUNCTION_ARGS); 186 | Datum pl_profiler_callgraph_shared(PG_FUNCTION_ARGS); 187 | Datum pl_profiler_func_oids_local(PG_FUNCTION_ARGS); 188 | Datum pl_profiler_func_oids_shared(PG_FUNCTION_ARGS); 189 | Datum pl_profiler_funcs_source(PG_FUNCTION_ARGS); 190 | Datum pl_profiler_reset_local(PG_FUNCTION_ARGS); 191 | Datum pl_profiler_reset_shared(PG_FUNCTION_ARGS); 192 | Datum pl_profiler_set_enabled_global(PG_FUNCTION_ARGS); 193 | Datum pl_profiler_get_enabled_global(PG_FUNCTION_ARGS); 194 | Datum pl_profiler_set_enabled_local(PG_FUNCTION_ARGS); 195 | Datum pl_profiler_get_enabled_local(PG_FUNCTION_ARGS); 196 | Datum pl_profiler_set_enabled_pid(PG_FUNCTION_ARGS); 197 | Datum pl_profiler_get_enabled_pid(PG_FUNCTION_ARGS); 198 | Datum pl_profiler_set_collect_interval(PG_FUNCTION_ARGS); 199 | Datum pl_profiler_get_collect_interval(PG_FUNCTION_ARGS); 200 | Datum pl_profiler_collect_data(PG_FUNCTION_ARGS); 201 | Datum pl_profiler_callgraph_overflow(PG_FUNCTION_ARGS); 202 | Datum pl_profiler_functions_overflow(PG_FUNCTION_ARGS); 203 | Datum pl_profiler_lines_overflow(PG_FUNCTION_ARGS); 204 | 205 | PG_FUNCTION_INFO_V1(pl_profiler_get_stack); 206 | PG_FUNCTION_INFO_V1(pl_profiler_linestats_local); 207 | PG_FUNCTION_INFO_V1(pl_profiler_linestats_shared); 208 | PG_FUNCTION_INFO_V1(pl_profiler_callgraph_local); 209 | PG_FUNCTION_INFO_V1(pl_profiler_callgraph_shared); 210 | PG_FUNCTION_INFO_V1(pl_profiler_func_oids_local); 211 | PG_FUNCTION_INFO_V1(pl_profiler_func_oids_shared); 212 | PG_FUNCTION_INFO_V1(pl_profiler_funcs_source); 213 | PG_FUNCTION_INFO_V1(pl_profiler_reset_local); 214 | PG_FUNCTION_INFO_V1(pl_profiler_reset_shared); 215 | PG_FUNCTION_INFO_V1(pl_profiler_set_enabled_global); 216 | PG_FUNCTION_INFO_V1(pl_profiler_get_enabled_global); 217 | PG_FUNCTION_INFO_V1(pl_profiler_set_enabled_local); 218 | PG_FUNCTION_INFO_V1(pl_profiler_get_enabled_local); 219 | PG_FUNCTION_INFO_V1(pl_profiler_set_enabled_pid); 220 | PG_FUNCTION_INFO_V1(pl_profiler_get_enabled_pid); 221 | PG_FUNCTION_INFO_V1(pl_profiler_set_collect_interval); 222 | PG_FUNCTION_INFO_V1(pl_profiler_get_collect_interval); 223 | PG_FUNCTION_INFO_V1(pl_profiler_collect_data); 224 | PG_FUNCTION_INFO_V1(pl_profiler_callgraph_overflow); 225 | PG_FUNCTION_INFO_V1(pl_profiler_functions_overflow); 226 | PG_FUNCTION_INFO_V1(pl_profiler_lines_overflow); 227 | 228 | #endif /* PLPROFILER_H */ 229 | -------------------------------------------------------------------------------- /python-plprofiler/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | dist 3 | *.egg-info 4 | -------------------------------------------------------------------------------- /python-plprofiler/MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include plprofiler/lib/FlameGraph * 2 | -------------------------------------------------------------------------------- /python-plprofiler/README.md: -------------------------------------------------------------------------------- 1 | PL/pgSQL Profiler module and command line tool 2 | ============================================== 3 | 4 | This is the Python module and command line tool to 5 | control the PL/pgSQL Profiler extension for PostgreSQL. 6 | 7 | Please visit https://github.com/bigsql/plprofiler for 8 | the main project. 9 | -------------------------------------------------------------------------------- /python-plprofiler/plprofiler/__init__.py: -------------------------------------------------------------------------------- 1 | from .plprofiler_tool import main 2 | from .plprofiler import plprofiler 3 | -------------------------------------------------------------------------------- /python-plprofiler/plprofiler/lib/FlameGraph/README: -------------------------------------------------------------------------------- 1 | Flame Graphs visualize profiled code-paths. 2 | 3 | Website: http://www.brendangregg.com/flamegraphs.html 4 | 5 | CPU profiling using DTrace, perf_events, SystemTap, or ktap: http://www.brendangregg.com/FlameGraphs/cpuflamegraphs.html 6 | CPU profiling using XCode Instruments: http://schani.wordpress.com/2012/11/16/flame-graphs-for-instruments/ 7 | CPU profiling using Xperf.exe: http://randomascii.wordpress.com/2013/03/26/summarizing-xperf-cpu-usage-with-flame-graphs/ 8 | Memory profiling: http://www.brendangregg.com/FlameGraphs/memoryflamegraphs.html 9 | 10 | These can be created in three steps: 11 | 12 | 1. Capture stacks 13 | 2. Fold stacks 14 | 3. flamegraph.pl 15 | 16 | 17 | 1. Capture stacks 18 | ================= 19 | Stack samples can be captured using DTrace, perf_events, SystemTap, pmcstat, 20 | Xperf, Intel VTune, or anything else than can capture full stack samples. 21 | 22 | Using DTrace to capture 60 seconds of kernel stacks at 997 Hertz: 23 | 24 | # dtrace -x stackframes=100 -n 'profile-997 /arg0/ { @[stack()] = count(); } tick-60s { exit(0); }' -o out.kern_stacks 25 | 26 | Using DTrace to capture 60 seconds of user-level stacks for PID 12345 at 97 Hertz: 27 | 28 | # dtrace -x ustackframes=100 -n 'profile-97 /pid == 12345 && arg1/ { @[ustack()] = count(); } tick-60s { exit(0); }' -o out.user_stacks 29 | 30 | Using DTrace to capture 60 seconds of user-level stacks, including while time is spent in the kernel, for PID 12345 at 97 Hertz: 31 | 32 | # dtrace -x ustackframes=100 -n 'profile-97 /pid == 12345/ { @[ustack()] = count(); } tick-60s { exit(0); }' -o out.user_stacks 33 | 34 | Switch ustack() for jstack() if the application has a ustack helper to include translated frames (eg, node.js frames; see: http://dtrace.org/blogs/dap/2012/01/05/where-does-your-node-program-spend-its-time/). The rate for user-level stack collection is deliberately slower than kernel, which is especially important when using jstack() as it performs additional work to translate frames. 35 | 36 | 2. Fold stacks 37 | ============== 38 | Use the stackcollapse programs to fold stack samples into single lines. The programs provided are: 39 | 40 | - stackcollapse.pl: for DTrace stacks 41 | - stackcollapse-perf.pl: for perf_events "perf script" output 42 | - stackcollapse-pmc.pl: for FreeBSD pmcstat -G stacks 43 | - stackcollapse-stap.pl: for SystemTap stacks 44 | - stackcollapse-instruments.pl: for XCode Instruments 45 | 46 | Usage example: 47 | 48 | $ ./stackcollapse.pl out.kern_stacks > out.kern_folded 49 | 50 | The output looks like this: 51 | 52 | unix`_sys_sysenter_post_swapgs 1401 53 | unix`_sys_sysenter_post_swapgs;genunix`close 5 54 | unix`_sys_sysenter_post_swapgs;genunix`close;genunix`closeandsetf 85 55 | unix`_sys_sysenter_post_swapgs;genunix`close;genunix`closeandsetf;c2audit`audit_closef 26 56 | unix`_sys_sysenter_post_swapgs;genunix`close;genunix`closeandsetf;c2audit`audit_setf 5 57 | unix`_sys_sysenter_post_swapgs;genunix`close;genunix`closeandsetf;genunix`audit_getstate 6 58 | unix`_sys_sysenter_post_swapgs;genunix`close;genunix`closeandsetf;genunix`audit_unfalloc 2 59 | unix`_sys_sysenter_post_swapgs;genunix`close;genunix`closeandsetf;genunix`closef 48 60 | [...] 61 | 62 | 3. flamegraph.pl 63 | ================ 64 | Use flamegraph.pl to render a SVG. 65 | 66 | $ ./flamegraph.pl out.kern_folded > kernel.svg 67 | 68 | An advantage of having the folded input file (and why this is separate to flamegraph.pl) is that you can use grep for functions of interest. Eg: 69 | 70 | $ grep cpuid out.kern_folded | ./flamegraph.pl > cpuid.svg 71 | 72 | 73 | Provided Example 74 | ================ 75 | An example output from DTrace is included, both the captured stacks and 76 | the resulting Flame Graph. You can generate it yourself using: 77 | 78 | $ ./stackcollapse.pl example-stacks.txt | ./flamegraph.pl > example.svg 79 | 80 | This was from a particular performance investigation: the Flame Graph 81 | identified that CPU time was spent in the lofs module, and quantified 82 | that time. 83 | 84 | 85 | Options 86 | ======= 87 | See the USAGE message (--help) for options: 88 | 89 | USAGE: ./flamegraph.pl [options] infile > outfile.svg 90 | 91 | --titletext # change title text 92 | --width # width of image (default 1200) 93 | --height # height of each frame (default 16) 94 | --minwidth # omit smaller functions (default 0.1 pixels) 95 | --fonttype # font type (default "Verdana") 96 | --fontsize # font size (default 12) 97 | --countname # count type label (default "samples") 98 | --nametype # name type label (default "Function:") 99 | --colors # "hot", "mem", "io" palette (default "hot") 100 | --hash # colors are keyed by function name hash 101 | --cp # use consistent palette (palette.map) 102 | eg, 103 | ./flamegraph.pl --titletext="Flame Graph: malloc()" trace.txt > graph.svg 104 | 105 | As suggested in the example, flame graphs can process traces of any event, 106 | such as malloc()s, provided stack traces are gathered. 107 | 108 | 109 | Consistent Palette 110 | ================== 111 | If you use the --cp option, it will use the $colors selection and randomly 112 | generate the palette like normal. Any future flamegraphs created using the --cp 113 | option will use the same palette map. Any new symbols from future flamegraphs 114 | will have their colors randomly generated using the $colors selection. 115 | 116 | If you don't like the palette, just delete the palette.map file. 117 | 118 | This allows your to change your colorscheme between flamegraphs to make the 119 | differences REALLY stand out. 120 | 121 | Example: 122 | 123 | Say we have 2 captures, one with a problem, and one when it was working 124 | (whatever "it" is): 125 | 126 | cat working.folded | ./flamegraph.pl --cp > working.svg 127 | # this generates a palette.map, as per the normal random generated look. 128 | 129 | cat broken.folded | ./flamegraph.pl --cp --colors mem > broken.svg 130 | # this svg will use the same palette.map for the same events, but a very 131 | # different colorscheme for any new events. 132 | 133 | Take a look at the demo directory for an example: 134 | 135 | palette-example-working.svg 136 | palette-example-broken.svg 137 | -------------------------------------------------------------------------------- /python-plprofiler/plprofiler/lib/FlameGraph/README-plprofiler: -------------------------------------------------------------------------------- 1 | This directory is a stripped down to the bare minimum version 2 | of Brendan D. Gregg's famous FlameGraph script collection. 3 | 4 | The plprofiler produces the input format of flamegraph.pl. So 5 | nothing else from the project is really needed and therfore 6 | there is no need to include it in this embedded lib directory. 7 | 8 | If you are looking for the full FlameGraph package, please 9 | visit 10 | 11 | http://www.brendangregg.com/ 12 | -------------------------------------------------------------------------------- /python-plprofiler/plprofiler/lib/FlameGraph/flamegraph.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | # 3 | # flamegraph.pl flame stack grapher. 4 | # 5 | # This takes stack samples and renders a call graph, allowing hot functions 6 | # and codepaths to be quickly identified. Stack samples can be generated using 7 | # tools such as DTrace, perf, SystemTap, and Instruments. 8 | # 9 | # USAGE: ./flamegraph.pl [options] input.txt > graph.svg 10 | # 11 | # grep funcA input.txt | ./flamegraph.pl [options] > graph.svg 12 | # 13 | # Options are listed in the usage message (--help). 14 | # 15 | # The input is stack frames and sample counts formatted as single lines. Each 16 | # frame in the stack is semicolon separated, with a space and count at the end 17 | # of the line. These can be generated using DTrace with stackcollapse.pl, 18 | # and other tools using the stackcollapse variants. 19 | # 20 | # An optional extra column of counts can be provided to generate a differential 21 | # flame graph of the counts, colored red for more, and blue for less. This 22 | # can be useful when using flame graphs for non-regression testing. 23 | # See the header comment in the difffolded.pl program for instructions. 24 | # 25 | # The output graph shows relative presence of functions in stack samples. The 26 | # ordering on the x-axis has no meaning; since the data is samples, time order 27 | # of events is not known. The order used sorts function names alphabetically. 28 | # 29 | # While intended to process stack samples, this can also process stack traces. 30 | # For example, tracing stacks for memory allocation, or resource usage. You 31 | # can use --title to set the title to reflect the content, and --countname 32 | # to change "samples" to "bytes" etc. 33 | # 34 | # There are a few different palettes, selectable using --color. By default, 35 | # the colors are selected at random (except for differentials). Functions 36 | # called "-" will be printed gray, which can be used for stack separators (eg, 37 | # between user and kernel stacks). 38 | # 39 | # HISTORY 40 | # 41 | # This was inspired by Neelakanth Nadgir's excellent function_call_graph.rb 42 | # program, which visualized function entry and return trace events. As Neel 43 | # wrote: "The output displayed is inspired by Roch's CallStackAnalyzer which 44 | # was in turn inspired by the work on vftrace by Jan Boerhout". See: 45 | # https://blogs.oracle.com/realneel/entry/visualizing_callstacks_via_dtrace_and 46 | # 47 | # Copyright 2011 Joyent, Inc. All rights reserved. 48 | # Copyright 2011 Brendan Gregg. All rights reserved. 49 | # 50 | # CDDL HEADER START 51 | # 52 | # The contents of this file are subject to the terms of the 53 | # Common Development and Distribution License (the "License"). 54 | # You may not use this file except in compliance with the License. 55 | # 56 | # You can obtain a copy of the license at docs/cddl1.txt or 57 | # http://opensource.org/licenses/CDDL-1.0. 58 | # See the License for the specific language governing permissions 59 | # and limitations under the License. 60 | # 61 | # When distributing Covered Code, include this CDDL HEADER in each 62 | # file and include the License file at docs/cddl1.txt. 63 | # If applicable, add the following below this CDDL HEADER, with the 64 | # fields enclosed by brackets "[]" replaced with your own identifying 65 | # information: Portions Copyright [yyyy] [name of copyright owner] 66 | # 67 | # CDDL HEADER END 68 | # 69 | # 21-Nov-2013 Shawn Sterling Added consistent palette file option 70 | # 17-Mar-2013 Tim Bunce Added options and more tunables. 71 | # 15-Dec-2011 Dave Pacheco Support for frames with whitespace. 72 | # 10-Sep-2011 Brendan Gregg Created this. 73 | 74 | use strict; 75 | 76 | use Getopt::Long; 77 | 78 | # tunables 79 | my $encoding; 80 | my $fonttype = "Verdana"; 81 | my $imagewidth = 1200; # max width, pixels 82 | my $frameheight = 16; # max height is dynamic 83 | my $fontsize = 12; # base text size 84 | my $fontwidth = 0.59; # avg width relative to fontsize 85 | my $minwidth = 0.1; # min function width, pixels 86 | my $nametype = "Function:"; # what are the names in the data? 87 | my $countname = "samples"; # what are the counts in the data? 88 | my $colors = "hot"; # color theme 89 | my $bgcolor1 = "#eeeeee"; # background color gradient start 90 | my $bgcolor2 = "#eeeeb0"; # background color gradient stop 91 | my $nameattrfile; # file holding function attributes 92 | my $timemax; # (override the) sum of the counts 93 | my $factor = 1; # factor to scale counts by 94 | my $hash = 0; # color by function name 95 | my $palette = 0; # if we use consistent palettes (default off) 96 | my %palette_map; # palette map hash 97 | my $pal_file = "palette.map"; # palette map file name 98 | my $stackreverse = 0; # reverse stack order, switching merge end 99 | my $inverted = 0; # icicle graph 100 | my $negate = 0; # switch differential hues 101 | my $titletext = ""; # centered heading 102 | my $titledefault = "Flame Graph"; # overwritten by --title 103 | my $titleinverted = "Icicle Graph"; # " " 104 | 105 | GetOptions( 106 | 'fonttype=s' => \$fonttype, 107 | 'width=i' => \$imagewidth, 108 | 'height=i' => \$frameheight, 109 | 'encoding=s' => \$encoding, 110 | 'fontsize=f' => \$fontsize, 111 | 'fontwidth=f' => \$fontwidth, 112 | 'minwidth=f' => \$minwidth, 113 | 'title=s' => \$titletext, 114 | 'nametype=s' => \$nametype, 115 | 'countname=s' => \$countname, 116 | 'nameattr=s' => \$nameattrfile, 117 | 'total=s' => \$timemax, 118 | 'factor=f' => \$factor, 119 | 'colors=s' => \$colors, 120 | 'hash' => \$hash, 121 | 'cp' => \$palette, 122 | 'reverse' => \$stackreverse, 123 | 'inverted' => \$inverted, 124 | 'negate' => \$negate, 125 | ) or die < outfile.svg\n 127 | --title # change title text 128 | --width # width of image (default 1200) 129 | --height # height of each frame (default 16) 130 | --minwidth # omit smaller functions (default 0.1 pixels) 131 | --fonttype # font type (default "Verdana") 132 | --fontsize # font size (default 12) 133 | --countname # count type label (default "samples") 134 | --nametype # name type label (default "Function:") 135 | --colors # set color palette. choices are: hot (default), mem, io, 136 | # java, js, red, green, blue, yellow, purple, orange 137 | --hash # colors are keyed by function name hash 138 | --cp # use consistent palette (palette.map) 139 | --reverse # generate stack-reversed flame graph 140 | --inverted # icicle graph 141 | --negate # switch differential hues (blue<->red) 142 | 143 | eg, 144 | $0 --title="Flame Graph: malloc()" trace.txt > graph.svg 145 | USAGE_END 146 | 147 | # internals 148 | my $ypad1 = $fontsize * 4; # pad top, include title 149 | my $ypad2 = $fontsize * 2 + 10; # pad bottom, include labels 150 | my $xpad = 10; # pad lefm and right 151 | my $framepad = 1; # vertical padding for frames 152 | my $depthmax = 0; 153 | my %Events; 154 | my %nameattr; 155 | 156 | if ($titletext eq "") { 157 | unless ($inverted) { 158 | $titletext = $titledefault; 159 | } else { 160 | $titletext = $titleinverted; 161 | } 162 | } 163 | 164 | if ($nameattrfile) { 165 | # The name-attribute file format is a function name followed by a tab then 166 | # a sequence of tab separated name=value pairs. 167 | open my $attrfh, $nameattrfile or die "Can't read $nameattrfile: $!\n"; 168 | while (<$attrfh>) { 169 | chomp; 170 | my ($funcname, $attrstr) = split /\t/, $_, 2; 171 | die "Invalid format in $nameattrfile" unless defined $attrstr; 172 | $nameattr{$funcname} = { map { split /=/, $_, 2 } split /\t/, $attrstr }; 173 | } 174 | } 175 | 176 | if ($colors eq "mem") { $bgcolor1 = "#eeeeee"; $bgcolor2 = "#e0e0ff"; } 177 | if ($colors eq "io") { $bgcolor1 = "#f8f8f8"; $bgcolor2 = "#e8e8e8"; } 178 | 179 | # SVG functions 180 | { package SVG; 181 | sub new { 182 | my $class = shift; 183 | my $self = {}; 184 | bless ($self, $class); 185 | return $self; 186 | } 187 | 188 | sub header { 189 | my ($self, $w, $h) = @_; 190 | my $enc_attr = ''; 191 | if (defined $encoding) { 192 | $enc_attr = qq{ encoding="$encoding"}; 193 | } 194 | $self->{svg} .= < 196 | 197 | 198 | SVG 199 | } 200 | 201 | sub include { 202 | my ($self, $content) = @_; 203 | $self->{svg} .= $content; 204 | } 205 | 206 | sub colorAllocate { 207 | my ($self, $r, $g, $b) = @_; 208 | return "rgb($r,$g,$b)"; 209 | } 210 | 211 | sub group_start { 212 | my ($self, $attr) = @_; 213 | 214 | my @g_attr = map { 215 | exists $attr->{$_} ? sprintf(qq/$_="%s"/, $attr->{$_}) : () 216 | } qw(class style onmouseover onmouseout onclick); 217 | push @g_attr, $attr->{g_extra} if $attr->{g_extra}; 218 | $self->{svg} .= sprintf qq/\n/, join(' ', @g_attr); 219 | 220 | $self->{svg} .= sprintf qq/%s<\/title>/, $attr->{title} 221 | if $attr->{title}; # should be first element within g container 222 | 223 | if ($attr->{href}) { 224 | my @a_attr; 225 | push @a_attr, sprintf qq/xlink:href="%s"/, $attr->{href} if $attr->{href}; 226 | # default target=_top else links will open within SVG 227 | push @a_attr, sprintf qq/target="%s"/, $attr->{target} || "_top"; 228 | push @a_attr, $attr->{a_extra} if $attr->{a_extra}; 229 | $self->{svg} .= sprintf qq//, join(' ', @a_attr); 230 | } 231 | } 232 | 233 | sub group_end { 234 | my ($self, $attr) = @_; 235 | $self->{svg} .= qq/<\/a>\n/ if $attr->{href}; 236 | $self->{svg} .= qq/<\/g>\n/; 237 | } 238 | 239 | sub filledRectangle { 240 | my ($self, $x1, $y1, $x2, $y2, $fill, $extra) = @_; 241 | $x1 = sprintf "%0.1f", $x1; 242 | $x2 = sprintf "%0.1f", $x2; 243 | my $w = sprintf "%0.1f", $x2 - $x1; 244 | my $h = sprintf "%0.1f", $y2 - $y1; 245 | $extra = defined $extra ? $extra : ""; 246 | $self->{svg} .= qq/\n/; 247 | } 248 | 249 | sub stringTTF { 250 | my ($self, $color, $font, $size, $angle, $x, $y, $str, $loc, $extra) = @_; 251 | $x = sprintf "%0.2f", $x; 252 | $loc = defined $loc ? $loc : "left"; 253 | $extra = defined $extra ? $extra : ""; 254 | $self->{svg} .= qq/$str<\/text>\n/; 255 | } 256 | 257 | sub svg { 258 | my $self = shift; 259 | return "$self->{svg}\n"; 260 | } 261 | 1; 262 | } 263 | 264 | sub namehash { 265 | # Generate a vector hash for the name string, weighting early over 266 | # later characters. We want to pick the same colors for function 267 | # names across different flame graphs. 268 | my $name = shift; 269 | my $vector = 0; 270 | my $weight = 1; 271 | my $max = 1; 272 | my $mod = 10; 273 | # if module name present, trunc to 1st char 274 | $name =~ s/.(.*?)`//; 275 | foreach my $c (split //, $name) { 276 | my $i = (ord $c) % $mod; 277 | $vector += ($i / ($mod++ - 1)) * $weight; 278 | $max += 1 * $weight; 279 | $weight *= 0.70; 280 | last if $mod > 12; 281 | } 282 | return (1 - $vector / $max) 283 | } 284 | 285 | sub color { 286 | my ($type, $hash, $name) = @_; 287 | my ($v1, $v2, $v3); 288 | 289 | if ($hash) { 290 | $v1 = namehash($name); 291 | $v2 = $v3 = namehash(scalar reverse $name); 292 | } else { 293 | $v1 = rand(1); 294 | $v2 = rand(1); 295 | $v3 = rand(1); 296 | } 297 | 298 | # theme palettes 299 | if (defined $type and $type eq "hot") { 300 | my $r = 205 + int(50 * $v3); 301 | my $g = 0 + int(230 * $v1); 302 | my $b = 0 + int(55 * $v2); 303 | return "rgb($r,$g,$b)"; 304 | } 305 | if (defined $type and $type eq "mem") { 306 | my $r = 0; 307 | my $g = 190 + int(50 * $v2); 308 | my $b = 0 + int(210 * $v1); 309 | return "rgb($r,$g,$b)"; 310 | } 311 | if (defined $type and $type eq "io") { 312 | my $r = 80 + int(60 * $v1); 313 | my $g = $r; 314 | my $b = 190 + int(55 * $v2); 315 | return "rgb($r,$g,$b)"; 316 | } 317 | 318 | # multi palettes 319 | if (defined $type and $type eq "java") { 320 | if ($name =~ /::/) { # C++ 321 | $type = "yellow"; 322 | } elsif ($name =~ m:/:) { # Java (match "/" in path) 323 | $type = "green" 324 | } else { # system 325 | $type = "red"; 326 | } 327 | # fall-through to color palettes 328 | } 329 | if (defined $type and $type eq "js") { 330 | if ($name =~ /::/) { # C++ 331 | $type = "yellow"; 332 | } elsif ($name =~ m:/:) { # JavaScript (match "/" in path) 333 | $type = "green" 334 | } elsif ($name =~ m/:/) { # JavaScript (match ":" in builtin) 335 | $type = "aqua" 336 | } elsif ($name =~ m/^ $/) { # Missing symbol 337 | $type = "green" 338 | } else { # system 339 | $type = "red"; 340 | } 341 | # fall-through to color palettes 342 | } 343 | 344 | # color palettes 345 | if (defined $type and $type eq "red") { 346 | my $r = 200 + int(55 * $v1); 347 | my $x = 50 + int(80 * $v1); 348 | return "rgb($r,$x,$x)"; 349 | } 350 | if (defined $type and $type eq "green") { 351 | my $g = 200 + int(55 * $v1); 352 | my $x = 50 + int(60 * $v1); 353 | return "rgb($x,$g,$x)"; 354 | } 355 | if (defined $type and $type eq "blue") { 356 | my $b = 205 + int(50 * $v1); 357 | my $x = 80 + int(60 * $v1); 358 | return "rgb($x,$x,$b)"; 359 | } 360 | if (defined $type and $type eq "yellow") { 361 | my $x = 175 + int(55 * $v1); 362 | my $b = 50 + int(20 * $v1); 363 | return "rgb($x,$x,$b)"; 364 | } 365 | if (defined $type and $type eq "purple") { 366 | my $x = 190 + int(65 * $v1); 367 | my $g = 80 + int(60 * $v1); 368 | return "rgb($x,$g,$x)"; 369 | } 370 | if (defined $type and $type eq "aqua") { 371 | my $r = 50 + int(60 * $v1); 372 | my $g = 165 + int(55 * $v1); 373 | my $b = 165 + int(55 * $v1); 374 | return "rgb($r,$g,$b)"; 375 | } 376 | if (defined $type and $type eq "orange") { 377 | my $r = 190 + int(65 * $v1); 378 | my $g = 90 + int(65 * $v1); 379 | return "rgb($r,$g,0)"; 380 | } 381 | 382 | return "rgb(0,0,0)"; 383 | } 384 | 385 | sub color_scale { 386 | my ($value, $max) = @_; 387 | my ($r, $g, $b) = (255, 255, 255); 388 | $value = -$value if $negate; 389 | if ($value > 0) { 390 | $g = $b = int(210 * ($max - $value) / $max); 391 | } elsif ($value < 0) { 392 | $r = $g = int(210 * ($max + $value) / $max); 393 | } 394 | return "rgb($r,$g,$b)"; 395 | } 396 | 397 | sub color_map { 398 | my ($colors, $func) = @_; 399 | if (exists $palette_map{$func}) { 400 | return $palette_map{$func}; 401 | } else { 402 | $palette_map{$func} = color($colors); 403 | return $palette_map{$func}; 404 | } 405 | } 406 | 407 | sub write_palette { 408 | open(FILE, ">$pal_file"); 409 | foreach my $key (sort keys %palette_map) { 410 | print FILE $key."->".$palette_map{$key}."\n"; 411 | } 412 | close(FILE); 413 | } 414 | 415 | sub read_palette { 416 | if (-e $pal_file) { 417 | open(FILE, $pal_file) or die "can't open file $pal_file: $!"; 418 | while ( my $line = ) { 419 | chomp($line); 420 | (my $key, my $value) = split("->",$line); 421 | $palette_map{$key}=$value; 422 | } 423 | close(FILE) 424 | } 425 | } 426 | 427 | my %Node; # Hash of merged frame data 428 | my %Tmp; 429 | 430 | # flow() merges two stacks, storing the merged frames and value data in %Node. 431 | sub flow { 432 | my ($last, $this, $v, $d) = @_; 433 | 434 | my $len_a = @$last - 1; 435 | my $len_b = @$this - 1; 436 | 437 | my $i = 0; 438 | my $len_same; 439 | for (; $i <= $len_a; $i++) { 440 | last if $i > $len_b; 441 | last if $last->[$i] ne $this->[$i]; 442 | } 443 | $len_same = $i; 444 | 445 | for ($i = $len_a; $i >= $len_same; $i--) { 446 | my $k = "$last->[$i];$i"; 447 | # a unique ID is constructed from "func;depth;etime"; 448 | # func-depth isn't unique, it may be repeated later. 449 | $Node{"$k;$v"}->{stime} = delete $Tmp{$k}->{stime}; 450 | if (defined $Tmp{$k}->{delta}) { 451 | $Node{"$k;$v"}->{delta} = delete $Tmp{$k}->{delta}; 452 | } 453 | delete $Tmp{$k}; 454 | } 455 | 456 | for ($i = $len_same; $i <= $len_b; $i++) { 457 | my $k = "$this->[$i];$i"; 458 | $Tmp{$k}->{stime} = $v; 459 | if (defined $d) { 460 | $Tmp{$k}->{delta} += $i == $len_b ? $d : 0; 461 | } 462 | } 463 | 464 | return $this; 465 | } 466 | 467 | # parse input 468 | my @Data; 469 | my $last = []; 470 | my $time = 0; 471 | my $delta = undef; 472 | my $ignored = 0; 473 | my $line; 474 | my $maxdelta = 1; 475 | 476 | # reverse if needed 477 | foreach (<>) { 478 | chomp; 479 | $line = $_; 480 | if ($stackreverse) { 481 | # there may be an extra samples column for differentials 482 | # XXX todo: redo these REs as one. It's repeated below. 483 | my ($stack, $samples) = (/^(.*)\s+?(\d+(?:\.\d*)?)$/); 484 | my $samples2 = undef; 485 | if ($stack =~ /^(.*)\s+?(\d+(?:\.\d*)?)$/) { 486 | $samples2 = $samples; 487 | ($stack, $samples) = $stack =~ (/^(.*)\s+?(\d+(?:\.\d*)?)$/); 488 | unshift @Data, join(";", reverse split(";", $stack)) . " $samples $samples2"; 489 | } else { 490 | unshift @Data, join(";", reverse split(";", $stack)) . " $samples"; 491 | } 492 | } else { 493 | unshift @Data, $line; 494 | } 495 | } 496 | 497 | # process and merge frames 498 | foreach (sort @Data) { 499 | chomp; 500 | # process: folded_stack count 501 | # eg: func_a;func_b;func_c 31 502 | my ($stack, $samples) = (/^(.*)\s+?(\d+(?:\.\d*)?)$/); 503 | unless (defined $samples and defined $stack) { 504 | ++$ignored; 505 | next; 506 | } 507 | 508 | # there may be an extra samples column for differentials: 509 | my $samples2 = undef; 510 | if ($stack =~ /^(.*)\s+?(\d+(?:\.\d*)?)$/) { 511 | $samples2 = $samples; 512 | ($stack, $samples) = $stack =~ (/^(.*)\s+?(\d+(?:\.\d*)?)$/); 513 | } 514 | $delta = undef; 515 | if (defined $samples2) { 516 | $delta = $samples2 - $samples; 517 | $maxdelta = abs($delta) if abs($delta) > $maxdelta; 518 | } 519 | 520 | $stack =~ tr/<>/()/; 521 | 522 | # merge frames and populate %Node: 523 | $last = flow($last, [ '', split ";", $stack ], $time, $delta); 524 | 525 | if (defined $samples2) { 526 | $time += $samples2; 527 | } else { 528 | $time += $samples; 529 | } 530 | } 531 | flow($last, [], $time, $delta); 532 | 533 | warn "Ignored $ignored lines with invalid format\n" if $ignored; 534 | unless ($time) { 535 | warn "ERROR: No stack counts found\n"; 536 | my $im = SVG->new(); 537 | # emit an error message SVG, for tools automating flamegraph use 538 | my $imageheight = $fontsize * 5; 539 | $im->header($imagewidth, $imageheight); 540 | $im->stringTTF($im->colorAllocate(0, 0, 0), $fonttype, $fontsize + 2, 541 | 0.0, int($imagewidth / 2), $fontsize * 2, 542 | "ERROR: No valid input provided to flamegraph.pl.", "middle"); 543 | print $im->svg; 544 | exit 2; 545 | } 546 | if ($timemax and $timemax < $time) { 547 | warn "Specified --total $timemax is less than actual total $time, so ignored\n" 548 | if $timemax/$time > 0.02; # only warn is significant (e.g., not rounding etc) 549 | undef $timemax; 550 | } 551 | $timemax ||= $time; 552 | 553 | my $widthpertime = ($imagewidth - 2 * $xpad) / $timemax; 554 | my $minwidth_time = $minwidth / $widthpertime; 555 | 556 | # prune blocks that are too narrow and determine max depth 557 | while (my ($id, $node) = each %Node) { 558 | my ($func, $depth, $etime) = split ";", $id; 559 | my $stime = $node->{stime}; 560 | die "missing start for $id" if not defined $stime; 561 | 562 | if (($etime-$stime) < $minwidth_time) { 563 | delete $Node{$id}; 564 | next; 565 | } 566 | $depthmax = $depth if $depth > $depthmax; 567 | } 568 | 569 | # draw canvas, and embed interactive JavaScript program 570 | my $imageheight = ($depthmax * $frameheight) + $ypad1 + $ypad2; 571 | my $im = SVG->new(); 572 | $im->header($imagewidth, $imageheight); 573 | my $inc = < 575 | 576 | 577 | 578 | 579 | 580 | 583 | 747 | INC 748 | $im->include($inc); 749 | $im->filledRectangle(0, 0, $imagewidth, $imageheight, 'url(#background)'); 750 | my ($white, $black, $vvdgrey, $vdgrey) = ( 751 | $im->colorAllocate(255, 255, 255), 752 | $im->colorAllocate(0, 0, 0), 753 | $im->colorAllocate(40, 40, 40), 754 | $im->colorAllocate(160, 160, 160), 755 | ); 756 | $im->stringTTF($black, $fonttype, $fontsize + 5, 0.0, int($imagewidth / 2), $fontsize * 2, $titletext, "middle"); 757 | $im->stringTTF($black, $fonttype, $fontsize, 0.0, $xpad, $imageheight - ($ypad2 / 2), " ", "", 'id="details"'); 758 | $im->stringTTF($black, $fonttype, $fontsize, 0.0, $xpad, $fontsize * 2, 759 | "Reset Zoom", "", 'id="unzoom" onclick="unzoom()" style="opacity:0.0;cursor:pointer"'); 760 | 761 | if ($palette) { 762 | read_palette(); 763 | } 764 | 765 | # draw frames 766 | while (my ($id, $node) = each %Node) { 767 | my ($func, $depth, $etime) = split ";", $id; 768 | my $stime = $node->{stime}; 769 | my $delta = $node->{delta}; 770 | 771 | $etime = $timemax if $func eq "" and $depth == 0; 772 | 773 | my $x1 = $xpad + $stime * $widthpertime; 774 | my $x2 = $xpad + $etime * $widthpertime; 775 | my ($y1, $y2); 776 | unless ($inverted) { 777 | $y1 = $imageheight - $ypad2 - ($depth + 1) * $frameheight + $framepad; 778 | $y2 = $imageheight - $ypad2 - $depth * $frameheight; 779 | } else { 780 | $y1 = $ypad1 + $depth * $frameheight; 781 | $y2 = $ypad1 + ($depth + 1) * $frameheight - $framepad; 782 | } 783 | 784 | my $samples = sprintf "%.0f", ($etime - $stime) * $factor; 785 | (my $samples_txt = $samples) # add commas per perlfaq5 786 | =~ s/(^[-+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/g; 787 | 788 | my $info; 789 | if ($func eq "" and $depth == 0) { 790 | $info = "all ($samples_txt $countname, 100%)"; 791 | } else { 792 | my $pct = sprintf "%.2f", ((100 * $samples) / ($timemax * $factor)); 793 | my $escaped_func = $func; 794 | $escaped_func =~ s/&/&/g; 795 | $escaped_func =~ s//>/g; 797 | unless (defined $delta) { 798 | $info = "$escaped_func ($samples_txt $countname, $pct%)"; 799 | } else { 800 | my $d = $negate ? -$delta : $delta; 801 | my $deltapct = sprintf "%.2f", ((100 * $d) / ($timemax * $factor)); 802 | $deltapct = $d > 0 ? "+$deltapct" : $deltapct; 803 | $info = "$escaped_func ($samples_txt $countname, $pct%; $deltapct%)"; 804 | } 805 | } 806 | 807 | my $nameattr = { %{ $nameattr{$func}||{} } }; # shallow clone 808 | $nameattr->{class} ||= "func_g"; 809 | $nameattr->{onmouseover} ||= "s('".$info."')"; 810 | $nameattr->{onmouseout} ||= "c()"; 811 | $nameattr->{onclick} ||= "zoom(this)"; 812 | $nameattr->{title} ||= $info; 813 | $im->group_start($nameattr); 814 | 815 | my $color; 816 | if ($func eq "-") { 817 | $color = $vdgrey; 818 | } elsif (defined $delta) { 819 | $color = color_scale($delta, $maxdelta); 820 | } elsif ($palette) { 821 | $color = color_map($colors, $func); 822 | } else { 823 | $color = color($colors, $hash, $func); 824 | } 825 | $im->filledRectangle($x1, $y1, $x2, $y2, $color, 'rx="2" ry="2"'); 826 | 827 | my $chars = int( ($x2 - $x1) / ($fontsize * $fontwidth)); 828 | my $text = ""; 829 | if ($chars >= 3) { # room for one char plus two dots 830 | $text = substr $func, 0, $chars; 831 | substr($text, -2, 2) = ".." if $chars < length $func; 832 | $text =~ s/&/&/g; 833 | $text =~ s//>/g; 835 | } 836 | $im->stringTTF($black, $fonttype, $fontsize, 0.0, $x1 + 3, 3 + ($y1 + $y2) / 2, $text, ""); 837 | 838 | $im->group_end($nameattr); 839 | } 840 | 841 | print $im->svg; 842 | 843 | if ($palette) { 844 | write_palette(); 845 | } 846 | 847 | # vim: ts=8 sts=8 sw=8 noexpandtab 848 | -------------------------------------------------------------------------------- /python-plprofiler/plprofiler/plprofiler_report.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import base64 4 | import html 5 | import os 6 | import subprocess 7 | import sys 8 | 9 | __all__ = ['plprofiler_report'] 10 | 11 | class plprofiler_report: 12 | def __init__(self): 13 | pass 14 | 15 | def generate(self, report_data, outfd): 16 | config = report_data['config'] 17 | self.outfd = outfd 18 | 19 | self.out("") 20 | self.out("") 21 | self.out(" %s" %(html.escape(config['title']), )) 22 | self.out(HTML_SCRIPT) 23 | self.out(HTML_STYLE) 24 | self.out("") 25 | self.out("""""") 26 | self.out(config['desc']) 27 | 28 | self.out("

PL/pgSQL Call Graph

") 29 | self.out("
") 30 | self.out(self.generate_flamegraph(config, report_data['flamedata'])) 31 | self.out("
") 32 | 33 | if not report_data['func_oids_by_user']: 34 | if report_data['found_more_funcs']: 35 | hdr = "

Top %d functions (by self_time)

" %(len(report_data['func_list']),) 36 | else: 37 | hdr = "

All %d functions (by self_time)

" %(len(report_data['func_list']),) 38 | else: 39 | hdr = "

Requested functions

" 40 | 41 | self.out("

List of functions detailed below

") 42 | self.out("
") 46 | self.out(hdr) 47 | 48 | for func_def in report_data['func_defs']: 49 | self.generate_function_output(config, func_def) 50 | 51 | self.out("") 52 | self.out("") 53 | 54 | def format_d_comma(self, num): 55 | s = str(num) 56 | r = [] 57 | l = len(s) 58 | i = 0 59 | j = l % 3 60 | if j == 0: 61 | j = 3 62 | while j <= l: 63 | r.append(s[i:j]) 64 | i = j 65 | j += 3 66 | return ",".join(r) 67 | 68 | def generate_function_output(self, config, func_def): 69 | func_def['self_time_fmt'] = self.format_d_comma(func_def['self_time']) 70 | func_def['total_time_fmt'] = self.format_d_comma(func_def['total_time']) 71 | self.out("""""".format(**func_def)) 72 | self.out("""

Function {schema}.{funcname}() oid={funcoid} (show)

""".format(**func_def)) 74 | self.out("""

""") 75 | self.out("""self_time = {self_time_fmt:s} µs
""".format(**func_def)) 76 | self.out("""total_time = {total_time_fmt:s} µs""".format(**func_def)) 77 | self.out("""

""") 78 | self.out("""""") 79 | self.out(""" """) 80 | self.out(""" """.format(**func_def)) 81 | self.out(""" """.format( 82 | funcargs = func_def['funcargs'].replace(', ', ',
 '))) 83 | self.out(""" """) 84 | self.out(""" """) 85 | self.out(""" """) 89 | self.out(""" """) 90 | self.out("""
{schema}.{funcname} ({funcargs})
""") 86 | self.out("""     RETURNS {funcresult}""".format( 87 | funcresult = func_def['funcresult'].replace(' ', ' '))) 88 | self.out("""
""") 91 | 92 | self.out("""") 121 | 122 | def generate_flamegraph(self, config, data): 123 | path = os.path.dirname(os.path.abspath(__file__)) 124 | path = os.path.join(path, 'lib', 'FlameGraph', 'flamegraph.pl', ) 125 | 126 | proc = subprocess.Popen(['perl', path, 127 | "--title=%s" %(config['title'], ), 128 | "--width=%s" %(config['svg_width'], ), ], 129 | stdin = subprocess.PIPE, 130 | stdout = subprocess.PIPE, 131 | stderr = subprocess.PIPE); 132 | svg, err = proc.communicate(data.encode('utf-8')) 133 | 134 | if proc.returncode != 0: 135 | raise Exception("flamegraph returned with exit code %d\n%s" %( 136 | proc.returncode, str(err))) 137 | return svg.decode('utf-8') 138 | 139 | def out(self, line): 140 | self.outfd.write(line + '\n') 141 | 142 | HTML_SCRIPT = """ 143 | 212 | """ 213 | 214 | HTML_STYLE = """ 215 | 256 | """ 257 | 258 | -------------------------------------------------------------------------------- /python-plprofiler/plprofiler/sql_split.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import re 4 | import sys 5 | 6 | def main(): 7 | for fname in sys.argv[1:]: 8 | with open(fname, 'r') as fd: 9 | statements = sql_split(fd.read()).statements 10 | # print statements 11 | 12 | class sql_split: 13 | def __init__(self, sql_str): 14 | self.sql_str = sql_str 15 | self.sql_len = len(sql_str) 16 | self.sql_idx = 0 17 | 18 | self.statements = [] 19 | self.cur_stmt = "" 20 | self.paren_level = 0 21 | self.dol_quote_re = re.compile("(\\$[^$\\s]*\\$)") 22 | 23 | self.state_normal() 24 | 25 | if not self.cur_stmt.isspace(): 26 | self.have_stmt() 27 | 28 | def get_statements(self): 29 | return self.statements 30 | 31 | def state_normal(self): 32 | while self.sql_idx < self.sql_len: 33 | if self.sql_str[self.sql_idx] == "'": 34 | self.state_quote() 35 | elif self.sql_str[self.sql_idx] == '"': 36 | self.state_d_quote() 37 | elif self.sql_str[self.sql_idx:self.sql_idx + 2] == "--": 38 | self.state_sql_comment() 39 | elif self.sql_str[self.sql_idx:self.sql_idx + 2] == "/*": 40 | self.state_C_comment() 41 | elif self.sql_str[self.sql_idx:self.sql_idx + 2] == "E'": 42 | self.state_e_quote("E'") 43 | elif self.sql_str[self.sql_idx:self.sql_idx + 2] == "e'": 44 | self.state_e_quote("e'") 45 | elif self.sql_str[self.sql_idx] == "(": 46 | self.cur_stmt += "(" 47 | self.sql_idx += 1 48 | self.paren_level += 1 49 | elif self.sql_str[self.sql_idx] == ")": 50 | self.cur_stmt += ")" 51 | self.sql_idx += 1 52 | self.paren_level -= 1 53 | elif self.sql_str[self.sql_idx] == "[": 54 | self.cur_stmt += "[" 55 | self.sql_idx += 1 56 | self.paren_level += 1 57 | elif self.sql_str[self.sql_idx] == "]": 58 | self.cur_stmt += "]" 59 | self.sql_idx += 1 60 | self.paren_level -= 1 61 | elif self.sql_str[self.sql_idx] == ";": 62 | self.cur_stmt += ";" 63 | self.sql_idx += 1 64 | if self.paren_level == 0: 65 | self.have_stmt() 66 | else: 67 | m = self.dol_quote_re.match(self.sql_str[self.sql_idx:]) 68 | if m is not None: 69 | self.state_dollar_quote(m.groups()[0]) 70 | else: 71 | self.cur_stmt += self.sql_str[self.sql_idx] 72 | self.sql_idx += 1 73 | 74 | def have_stmt(self): 75 | if self.cur_stmt.strip() != "": 76 | self.statements.append(self.cur_stmt) 77 | self.cur_stmt = "" 78 | while self.sql_idx < self.sql_len: 79 | if self.sql_str[self.sql_idx].isspace(): 80 | self.sql_idx += 1 81 | else: 82 | break 83 | 84 | def state_sql_comment(self): 85 | self.cur_stmt += "--" 86 | self.sql_idx += 2 87 | while self.sql_idx < self.sql_len: 88 | if self.sql_str[self.sql_idx:self.sql_idx + 2] == '\r\n': 89 | self.cur_stmt += '\r\n' 90 | self.sql_idx += 2 91 | return 92 | elif self.sql_str[self.sql_idx] == '\n': 93 | self.cur_stmt += '\n' 94 | self.sql_idx += 1 95 | return 96 | else: 97 | self.cur_stmt += self.sql_str[self.sql_idx] 98 | self.sql_idx += 1 99 | 100 | def state_C_comment(self): 101 | self.cur_stmt += "/*" 102 | self.sql_idx += 2 103 | while self.sql_idx < self.sql_len: 104 | if self.sql_str[self.sql_idx:self.sql_idx + 2] == '*/': 105 | self.cur_stmt += '*/' 106 | self.sql_idx += 2 107 | return 108 | else: 109 | self.cur_stmt += self.sql_str[self.sql_idx] 110 | self.sql_idx += 1 111 | 112 | def state_quote(self): 113 | self.cur_stmt += "'" 114 | self.sql_idx += 1 115 | while self.sql_idx < self.sql_len: 116 | if self.sql_str[self.sql_idx:self.sql_idx + 2] == "''": 117 | self.cur_stmt += "''" 118 | self.sql_idx += 2 119 | elif self.sql_str[self.sql_idx] == "'": 120 | self.cur_stmt += "'" 121 | self.sql_idx += 1 122 | return 123 | else: 124 | self.cur_stmt += self.sql_str[self.sql_idx] 125 | self.sql_idx += 1 126 | 127 | def state_d_quote(self): 128 | self.cur_stmt += '"' 129 | self.sql_idx += 1 130 | while self.sql_idx < self.sql_len: 131 | if self.sql_str[self.sql_idx:self.sql_idx + 2] == '""': 132 | self.cur_stmt += '""' 133 | self.sql_idx += 2 134 | elif self.sql_str[self.sql_idx] == '"': 135 | self.cur_stmt += '"' 136 | self.sql_idx += 1 137 | return 138 | else: 139 | self.cur_stmt += self.sql_str[self.sql_idx] 140 | self.sql_idx += 1 141 | 142 | def state_e_quote(self): 143 | self.cur_stmt += "'" 144 | self.sql_idx += 1 145 | while self.sql_idx < self.sql_len: 146 | if self.sql_str[self.sql_idx] == '\\': 147 | self.cur_stmt += self.sql_str[self.sql_idx:self.sql_idx + 2] 148 | self.sql_idx += 2 149 | elif self.sql_str[self.sql_idx:self.sql_idx + 2] == "''": 150 | self.cur_stmt += "''" 151 | self.sql_idx += 2 152 | elif self.sql_str[self.sql_idx] == "'": 153 | self.cur_stmt += "'" 154 | self.sql_idx += 1 155 | return 156 | else: 157 | self.cur_stmt += self.sql_str[self.sql_idx] 158 | self.sql_idx += 1 159 | 160 | def state_dollar_quote(self, tag): 161 | tag_len = len(tag) 162 | self.cur_stmt += tag 163 | self.sql_idx += tag_len 164 | while self.sql_idx < self.sql_len: 165 | if self.sql_str[self.sql_idx:self.sql_idx + tag_len] == tag: 166 | self.cur_stmt += tag 167 | self.sql_idx += tag_len 168 | return 169 | self.cur_stmt += self.sql_str[self.sql_idx] 170 | self.sql_idx += 1 171 | 172 | if __name__ == '__main__': 173 | main() 174 | -------------------------------------------------------------------------------- /python-plprofiler/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name = 'plprofiler-client', 5 | description = 'PL/pgSQL Profiler module and command line tool', 6 | version = '4.2', 7 | author = 'Jan Wieck', 8 | author_email = 'jan@wi3ck.info', 9 | url = 'https://github.com/bigsql/plprofiler', 10 | license = 'Artistic License and CDDL', 11 | packages = ['plprofiler', ], 12 | long_description = """PL/pgSQL Profiler module and command line tool 13 | ============================================== 14 | 15 | This is the Python module and command line tool to 16 | control the PL/pgSQL Profiler extension for PostgreSQL. 17 | 18 | Please visit https://github.com/bigsql/plprofiler for 19 | the main project.""", 20 | long_description_content_type = 'text/markdown', 21 | package_data = { 22 | 'plprofiler': [ 23 | 'lib/FlameGraph/README', 24 | 'lib/FlameGraph/README-plprofiler', 25 | 'lib/FlameGraph/flamegraph.pl', 26 | ], 27 | }, 28 | install_requires = [ 29 | 'configparser', 30 | ], 31 | entry_points = { 32 | 'console_scripts': [ 33 | 'plprofiler = plprofiler:main', 34 | ] 35 | }, 36 | ) 37 | --------------------------------------------------------------------------------