├── .gitignore ├── README.md ├── assets ├── colResizable-1.6.min.js ├── favicon.ico ├── jquery-3.5.1.js ├── jquery.dataTables.min.css ├── jquery.dataTables.min.js └── style.css ├── config.yaml ├── config_parser.py ├── cpp_optimization_example ├── .gitignore ├── Makefile ├── main.cc └── run_optview2.sh ├── google39baaac72fbfa7f9.html ├── opt-viewer.py ├── optpmap.py ├── optrecord.py ├── requirements.txt └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | .idea/ 3 | .vscode 4 | __pycache__/ 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OptView2 - User-oriented fork of LLVM's opt-viewer 2 | 3 | ## Video Introduction 4 | https://www.youtube.com/watch?v=qmEsx4MbKoc 5 | 6 | The slides (with links) are available at https://github.com/CppCon/CppCon2022/blob/main/Presentations/Optimization-Remarks.pdf . 7 | 8 | This is still the best way to start, as the talk includes example script outputs and recommendations on handling them. The text below surveys background and technical usage. 9 | 10 | ## Text Introduction 11 | 12 | In the beginning there was the compiler switch [`-Rpass`](https://clang.llvm.org/docs/UsersManual.html#options-to-emit-optimization-reports), and it was good. Sorta. Clang users who wanted visibility into compiler optimization decisions could dump a wall of text and sift through it trying to make up what's important and what's actionable. 13 | Then, Adam Nemet et. al. added a compiler switch (`clang -fsave-optimization-record`) and the [opt-viewer python script](https://github.com/llvm/llvm-project/tree/main/llvm/tools/opt-viewer), as part of LLVM. He [presented it at the 2016 LLVM Developers’ Meeting](https://www.youtube.com/watch?v=qq0q1hfzidg), and lo it was good. Now users could generate and inspect HTMLs of their C/C++ sources, annotated with "optimization-remarks" in place. 14 | 15 | Alas, these tools were explicitly designed for use by compiler writers wishing to investigate and improve optimization code, with only a mention of future adaptation for usage by developers wishing to understand and improve their application's optimization. 16 | 17 | Hence the birth of OptView2. We aim to make this wonderful optimization data accessible and _actionable_ by developers. 18 | 19 | ### Main changes w.r.t. the LLVM origin 20 | 1) Ignore system headers, 21 | 2) Collect only optimization _failures_, 22 | 3) Display in index.html only a single entry per type/source loc, 23 | 4) Replace ‘pass’ with ‘optimization name’, 24 | 5) Make the index table sortable & resizable (Thanks [Ilan Ben-Hagai](https://github.com/supox)) 25 | 6) Use abridged func names. 26 | 7) Create option to split processing into subfolders ('--split-top-folders') to enable processing of large projects 27 | 8) Trim repeated remarks in source - keep only 5 per line. 28 | 9) Enable filtering by remark name/text, preferably via config file (but possible via command line too). Check `config.yaml` for some examples. 29 | 30 | 31 | ### Versioning 32 | I can't see any future potential compatibility considerations, and these are essentially just 5 python scripts and some html+javascript - so at this point there won't be any versioning or releases structure. Just download/clone and use - and please report any problems you come across. 33 | 34 | ### Performance 35 | It is not uncommon for an analysis of a ~1000 file project to take an hour or more. Two things can help mitigate the burden: 36 | 1) The `-j[N]` command line switch to opt-viewer.py controls the number of jobs to spawn for YAML processing. A rule of thumb that worked best for my PC was to set `N` to 1.5 times the number of physical cores (for an 8 core machine, set tot 12), but there's no real alternative to experimentation. By default, the number of jobs invoked equals the number of logical cores. 37 | 2) The script uses the python package PyYaml - which uses the C++ package libyaml if available, and if not - falls back to a much, much slower python implementation. In such a case you'd see this line in the script output: 38 | > For faster parsing, you may want to install libyaml for PyYAL 39 | 40 | One way to install and use libyaml is: 41 | ``` 42 | $ sudo apt install libyaml-dev 43 | $ pip --no-cache-dir install --verbose --force-reinstall -I pyyaml 44 | ``` 45 | 46 | ### Usage examples 47 | First, build your C/C++ project with Clang + `-fsave-optimization-record`. Note that by default this generates YAMLs alongside the obj files. Then - 48 | 49 | #### Basic usage 50 | ``` 51 | ./optview2/opt-viewer.py --output-dir --source-dir 52 | ``` 53 | Note that `` needs to be the original root of the build which included `-fsave-optimization-record`, even if you're interested only in part of the tree. Express this filter through the `` argument. 54 | #### Parallelize to 10 jobs: 55 | ``` 56 | ./optview2/opt-viewer.py -j10 --output-dir <...> --source-dir <...> 57 | ``` 58 | 59 | #### Split top-level folders: 60 | When working on large projects optview2's memory consumption easily gets out of hand. As a quick workaround, you can separate the work to build-subfolders (only first-level subfolders are supported). For example: 61 | ``` 62 | ./optview2/opt-viewer.py --split-top-folders --output-dir <...> --source-dir <...> 63 | ``` 64 | If, for example, the build dir includes subfolders "core", "utils" and "plugins" - the script would process them separately, and create 3 identically named subfolders under output-dir (with separate index files). 65 | If this doesn't work for you - you can also filter out comment types via remarks-filter. 66 | #### Sample projects 67 | A dummy project with a few optimization issues is placed under `cpp_optimization_example`. To compile, generate HTML files and open in browser, use the wrapper script: 68 | ``` 69 | ./optview2/cpp_optimization_example/run_optview2.sh 70 | ``` 71 | Note to WSL users: you'd [probably need to manually open the resulting HTML](https://github.com/OfekShilon/optview2/issues/11). 72 | 73 | Two real-life projects were analyzed and the results pushed online - check [CPython](https://ofekshilon.github.io/optview2-cpython/) and [OpenCV](https://ofekshilon.github.io/optview2-opencv/) pages. 74 | -------------------------------------------------------------------------------- /assets/colResizable-1.6.min.js: -------------------------------------------------------------------------------- 1 | // colResizable 1.6 - a jQuery plugin by Alvaro Prieto Lauroba http://www.bacubacu.com/colresizable/ 2 | 3 | !function(t){var e,i=t(document),r=t("head"),o=null,s={},d=0,n="id",a="px",l="JColResizer",c="JCLRFlex",f=parseInt,h=Math,p=navigator.userAgent.indexOf("Trident/4.0")>0;try{e=sessionStorage}catch(g){}r.append("");var u=function(e,i){var o=t(e);if(o.opt=i,o.mode=i.resizeMode,o.dc=o.opt.disabledColumns,o.opt.disable)return w(o);var a=o.id=o.attr(n)||l+d++;o.p=o.opt.postbackSafe,!o.is("table")||s[a]&&!o.opt.partialRefresh||("e-resize"!==o.opt.hoverCursor&&r.append(""),o.addClass(l).attr(n,a).before('
'),o.g=[],o.c=[],o.w=o.width(),o.gc=o.prev(),o.f=o.opt.fixed,i.marginLeft&&o.gc.css("marginLeft",i.marginLeft),i.marginRight&&o.gc.css("marginRight",i.marginRight),o.cs=f(p?e.cellSpacing||e.currentStyle.borderSpacing:o.css("border-spacing"))||2,o.b=f(p?e.border||e.currentStyle.borderLeftWidth:o.css("border-left-width"))||1,s[a]=o,v(o))},w=function(t){var e=t.attr(n),t=s[e];t&&t.is("table")&&(t.removeClass(l+" "+c).gc.remove(),delete s[e])},v=function(i){var r=i.find(">thead>tr:first>th,>thead>tr:first>td");r.length||(r=i.find(">tbody>tr:first>th,>tr:first>th,>tbody>tr:first>td, >tr:first>td")),r=r.filter(":visible"),i.cg=i.find("col"),i.ln=r.length,i.p&&e&&e[i.id]&&m(i,r),r.each(function(e){var r=t(this),o=-1!=i.dc.indexOf(e),s=t(i.gc.append('
')[0].lastChild);s.append(o?"":i.opt.gripInnerHtml).append('
'),e==i.ln-1&&(s.addClass("JCLRLastGrip"),i.f&&s.html("")),s.bind("touchstart mousedown",J),o?s.addClass("JCLRdisabledGrip"):s.removeClass("JCLRdisabledGrip").bind("touchstart mousedown",J),s.t=i,s.i=e,s.c=r,r.w=r.width(),i.g.push(s),i.c.push(r),r.width(r.w).removeAttr("width"),s.data(l,{i:e,t:i.attr(n),last:e==i.ln-1})}),i.cg.removeAttr("width"),i.find("td, th").not(r).not("table th, table td").each(function(){t(this).removeAttr("width")}),i.f||i.removeAttr("width").addClass(c),C(i)},m=function(t,i){var r,o,s=0,d=0,n=[];if(i){if(t.cg.removeAttr("width"),t.opt.flush)return void(e[t.id]="");for(r=e[t.id].split(";"),o=r[t.ln+1],!t.f&&o&&(t.width(o*=1),t.opt.overflow&&(t.css("min-width",o+a),t.w=o));d*{cursor:"+n.opt.dragCursor+"!important}"),a.addClass(n.opt.draggingClass),o=a,n.c[d.i].l)for(var f,h=0;h.sorting_1,table.dataTable.order-column tbody tr>.sorting_2,table.dataTable.order-column tbody tr>.sorting_3,table.dataTable.display tbody tr>.sorting_1,table.dataTable.display tbody tr>.sorting_2,table.dataTable.display tbody tr>.sorting_3{background-color:#fafafa}table.dataTable.order-column tbody tr.selected>.sorting_1,table.dataTable.order-column tbody tr.selected>.sorting_2,table.dataTable.order-column tbody tr.selected>.sorting_3,table.dataTable.display tbody tr.selected>.sorting_1,table.dataTable.display tbody tr.selected>.sorting_2,table.dataTable.display tbody tr.selected>.sorting_3{background-color:#acbad5}table.dataTable.display tbody tr.odd>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd>.sorting_1{background-color:#f1f1f1}table.dataTable.display tbody tr.odd>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd>.sorting_2{background-color:#f3f3f3}table.dataTable.display tbody tr.odd>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd>.sorting_3{background-color:whitesmoke}table.dataTable.display tbody tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_1{background-color:#a6b4cd}table.dataTable.display tbody tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_2{background-color:#a8b5cf}table.dataTable.display tbody tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_3{background-color:#a9b7d1}table.dataTable.display tbody tr.even>.sorting_1,table.dataTable.order-column.stripe tbody tr.even>.sorting_1{background-color:#fafafa}table.dataTable.display tbody tr.even>.sorting_2,table.dataTable.order-column.stripe tbody tr.even>.sorting_2{background-color:#fcfcfc}table.dataTable.display tbody tr.even>.sorting_3,table.dataTable.order-column.stripe tbody tr.even>.sorting_3{background-color:#fefefe}table.dataTable.display tbody tr.even.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_1{background-color:#acbad5}table.dataTable.display tbody tr.even.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_2{background-color:#aebcd6}table.dataTable.display tbody tr.even.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_3{background-color:#afbdd8}table.dataTable.display tbody tr:hover>.sorting_1,table.dataTable.order-column.hover tbody tr:hover>.sorting_1{background-color:#eaeaea}table.dataTable.display tbody tr:hover>.sorting_2,table.dataTable.order-column.hover tbody tr:hover>.sorting_2{background-color:#ececec}table.dataTable.display tbody tr:hover>.sorting_3,table.dataTable.order-column.hover tbody tr:hover>.sorting_3{background-color:#efefef}table.dataTable.display tbody tr:hover.selected>.sorting_1,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1{background-color:#a2aec7}table.dataTable.display tbody tr:hover.selected>.sorting_2,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2{background-color:#a3b0c9}table.dataTable.display tbody tr:hover.selected>.sorting_3,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3{background-color:#a5b2cb}table.dataTable.no-footer{border-bottom:1px solid #111}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable.compact thead th,table.dataTable.compact thead td{padding:4px 17px}table.dataTable.compact tfoot th,table.dataTable.compact tfoot td{padding:4px}table.dataTable.compact tbody th,table.dataTable.compact tbody td{padding:4px}table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center,table.dataTable td.dataTables_empty{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}table.dataTable,table.dataTable th,table.dataTable td{box-sizing:content-box}.dataTables_wrapper{position:relative;clear:both;*zoom:1;zoom:1}.dataTables_wrapper .dataTables_length{float:left}.dataTables_wrapper .dataTables_length select{border:1px solid #aaa;border-radius:3px;padding:5px;background-color:transparent;padding:4px}.dataTables_wrapper .dataTables_filter{float:right;text-align:right}.dataTables_wrapper .dataTables_filter input{border:1px solid #aaa;border-radius:3px;padding:5px;background-color:transparent;margin-left:3px}.dataTables_wrapper .dataTables_info{clear:both;float:left;padding-top:.755em}.dataTables_wrapper .dataTables_paginate{float:right;text-align:right;padding-top:.25em}.dataTables_wrapper .dataTables_paginate .paginate_button{box-sizing:border-box;display:inline-block;min-width:1.5em;padding:.5em 1em;margin-left:2px;text-align:center;text-decoration:none !important;cursor:pointer;*cursor:hand;color:#333 !important;border:1px solid transparent;border-radius:2px}.dataTables_wrapper .dataTables_paginate .paginate_button.current,.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover{color:#333 !important;border:1px solid #979797;background-color:white;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(100%, #dcdcdc));background:-webkit-linear-gradient(top, white 0%, #dcdcdc 100%);background:-moz-linear-gradient(top, white 0%, #dcdcdc 100%);background:-ms-linear-gradient(top, white 0%, #dcdcdc 100%);background:-o-linear-gradient(top, white 0%, #dcdcdc 100%);background:linear-gradient(to bottom, white 0%, #dcdcdc 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active{cursor:default;color:#666 !important;border:1px solid transparent;background:transparent;box-shadow:none}.dataTables_wrapper .dataTables_paginate .paginate_button:hover{color:white !important;border:1px solid #111;background-color:#585858;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));background:-webkit-linear-gradient(top, #585858 0%, #111 100%);background:-moz-linear-gradient(top, #585858 0%, #111 100%);background:-ms-linear-gradient(top, #585858 0%, #111 100%);background:-o-linear-gradient(top, #585858 0%, #111 100%);background:linear-gradient(to bottom, #585858 0%, #111 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button:active{outline:none;background-color:#2b2b2b;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));background:-webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);box-shadow:inset 0 0 3px #111}.dataTables_wrapper .dataTables_paginate .ellipsis{padding:0 1em}.dataTables_wrapper .dataTables_processing{position:absolute;top:50%;left:50%;width:100%;height:40px;margin-left:-50%;margin-top:-25px;padding-top:20px;text-align:center;font-size:1.2em;background-color:white;background:-webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0)));background:-webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);background:-moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);background:-ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);background:-o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%);background:linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%)}.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_processing,.dataTables_wrapper .dataTables_paginate{color:#333}.dataTables_wrapper .dataTables_scroll{clear:both}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody{*margin-top:-1px;-webkit-overflow-scrolling:touch}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td{vertical-align:middle}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td>div.dataTables_sizing{height:0;overflow:hidden;margin:0 !important;padding:0 !important}.dataTables_wrapper.no-footer .dataTables_scrollBody{border-bottom:1px solid #111}.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,.dataTables_wrapper.no-footer div.dataTables_scrollBody>table{border-bottom:none}.dataTables_wrapper:after{visibility:hidden;display:block;content:"";clear:both;height:0}@media screen and (max-width: 767px){.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_paginate{float:none;text-align:center}.dataTables_wrapper .dataTables_paginate{margin-top:.5em}}@media screen and (max-width: 640px){.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter{float:none;text-align:center}.dataTables_wrapper .dataTables_filter{margin-top:.5em}} 2 | -------------------------------------------------------------------------------- /assets/jquery.dataTables.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Copyright 2008-2021 SpryMedia Ltd. 3 | 4 | This source file is free software, available under the following license: 5 | MIT license - http://datatables.net/license 6 | 7 | This source file is distributed in the hope that it will be useful, but 8 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 9 | or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. 10 | 11 | For details please refer to: http://www.datatables.net 12 | DataTables 1.10.25 13 | ©2008-2021 SpryMedia Ltd - datatables.net/license 14 | */ 15 | var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.findInternal=function(k,y,z){k instanceof String&&(k=String(k));for(var q=k.length,G=0;G").css({position:"fixed",top:0,left:-1*k(y).scrollLeft(),height:1, 25 | width:1,overflow:"hidden"}).append(k("
").css({position:"absolute",top:1,left:1,width:100,overflow:"scroll"}).append(k("
").css({width:"100%",height:10}))).appendTo("body"),d=c.children(),e=d.children();b.barWidth=d[0].offsetWidth-d[0].clientWidth;b.bScrollOversize=100===e[0].offsetWidth&&100!==d[0].clientWidth;b.bScrollbarLeft=1!==Math.round(e.offset().left);b.bBounding=c[0].getBoundingClientRect().width?!0:!1;c.remove()}k.extend(a.oBrowser,u.__browser);a.oScroll.iBarWidth=u.__browser.barWidth} 26 | function Cb(a,b,c,d,e,f){var g=!1;if(c!==q){var h=c;g=!0}for(;d!==e;)a.hasOwnProperty(d)&&(h=g?b(h,a[d],d,a):a[d],g=!0,d+=f);return h}function Xa(a,b){var c=u.defaults.column,d=a.aoColumns.length;c=k.extend({},u.models.oColumn,c,{nTh:b?b:z.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.mData:d,idx:d});a.aoColumns.push(c);c=a.aoPreSearchCols;c[d]=k.extend({},u.models.oSearch,c[d]);Ea(a,d,k(b).data())}function Ea(a,b,c){b=a.aoColumns[b]; 27 | var d=a.oClasses,e=k(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=e.attr("width")||null;var f=(e.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);f&&(b.sWidthOrig=f[1])}c!==q&&null!==c&&(Ab(c),O(u.defaults.column,c,!0),c.mDataProp===q||c.mData||(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&&!c.sClass&&(c.sClass=c.className),c.sClass&&e.addClass(c.sClass),k.extend(b,c),V(b,c,"sWidth","sWidthOrig"),c.iDataSort!==q&&(b.aDataSort=[c.iDataSort]),V(b,c,"aDataSort"));var g=b.mData,h=ia(g), 28 | l=b.mRender?ia(b.mRender):null;c=function(n){return"string"===typeof n&&-1!==n.indexOf("@")};b._bAttrSrc=k.isPlainObject(g)&&(c(g.sort)||c(g.type)||c(g.filter));b._setter=null;b.fnGetData=function(n,m,p){var t=h(n,m,q,p);return l&&m?l(t,m,n,p):t};b.fnSetData=function(n,m,p){return da(g)(n,m,p)};"number"!==typeof g&&(a._rowReadObject=!0);a.oFeatures.bSort||(b.bSortable=!1,e.addClass(d.sSortableNone));a=-1!==k.inArray("asc",b.asSorting);c=-1!==k.inArray("desc",b.asSorting);b.bSortable&&(a||c)?a&&!c? 29 | (b.sSortingClass=d.sSortableAsc,b.sSortingClassJUI=d.sSortJUIAscAllowed):!a&&c?(b.sSortingClass=d.sSortableDesc,b.sSortingClassJUI=d.sSortJUIDescAllowed):(b.sSortingClass=d.sSortable,b.sSortingClassJUI=d.sSortJUI):(b.sSortingClass=d.sSortableNone,b.sSortingClassJUI="")}function ra(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;Ya(a);for(var c=0,d=b.length;cn[m])d(h.length+n[m],l);else if("string"===typeof n[m]){var p=0;for(g=h.length;pb&&a[e]--; -1!=d&&c===q&&a.splice(d,1)}function va(a,b,c,d){var e=a.aoData[b],f,g=function(l,n){for(;l.childNodes.length;)l.removeChild(l.firstChild);l.innerHTML=S(a,b,n,"display")};if("dom"!==c&&(c&&"auto"!==c||"dom"!==e.src)){var h=e.anCells;if(h)if(d!==q)g(h[d],d);else for(c=0,f=h.length;c").appendTo(d));var l=0;for(b=h.length;l=a.fnRecordsDisplay()?0:h,a.iInitDisplayStart=-1);h=a._iDisplayStart;var m=a.fnDisplayEnd();if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,U(a,!1);else if(!l)a.iDraw++;else if(!a.bDestroying&&!b){Gb(a);return}if(0!==n.length)for(b=l?a.aoData.length:m,g=l?0:h;g",{"class":f?e[0]:""}).append(k("",{valign:"top",colSpan:na(a),"class":a.oClasses.sRowEmpty}).html(d))[0];H(a,"aoHeaderCallback","header",[k(a.nTHead).children("tr")[0], 47 | cb(a),h,m,n]);H(a,"aoFooterCallback","footer",[k(a.nTFoot).children("tr")[0],cb(a),h,m,n]);e=k(a.nTBody);e.children().detach();e.append(k(c));H(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function ja(a,b){var c=a.oFeatures,d=c.bFilter;c.bSort&&Hb(a);d?ya(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;fa(a);a._drawHold=!1}function Ib(a){var b=a.oClasses,c=k(a.nTable);c=k("
").insertBefore(c);var d=a.oFeatures, 48 | e=k("
",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=e[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var f=a.sDom.split(""),g,h,l,n,m,p,t=0;t")[0];n=f[t+1];if("'"==n||'"'==n){m="";for(p=2;f[t+p]!=n;)m+=f[t+p],p++;"H"==m?m=b.sJUIHeader:"F"==m&&(m=b.sJUIFooter);-1!=m.indexOf(".")?(n=m.split("."),l.id=n[0].substr(1,n[0].length-1),l.className=n[1]):"#"==m.charAt(0)?l.id=m.substr(1, 49 | m.length-1):l.className=m;t+=p}e.append(l);e=k(l)}else if(">"==h)e=e.parent();else if("l"==h&&d.bPaginate&&d.bLengthChange)g=Jb(a);else if("f"==h&&d.bFilter)g=Kb(a);else if("r"==h&&d.bProcessing)g=Lb(a);else if("t"==h)g=Mb(a);else if("i"==h&&d.bInfo)g=Nb(a);else if("p"==h&&d.bPaginate)g=Ob(a);else if(0!==u.ext.feature.length)for(l=u.ext.feature,p=0,n=l.length;p',h=d.sSearch;h=h.match(/_INPUT_/)?h.replace("_INPUT_",g):h+g;b=k("
",{id:f.f?null:c+"_filter","class":b.sFilter}).append(k("
").addClass(b.sLength);a.aanFeatures.l||(l[0].id=c+"_length");l.children().append(a.oLanguage.sLengthMenu.replace("_MENU_", 67 | e[0].outerHTML));k("select",l).val(a._iDisplayLength).on("change.DT",function(n){jb(a,k(this).val());fa(a)});k(a.nTable).on("length.dt.DT",function(n,m,p){a===m&&k("select",l).val(p)});return l[0]}function Ob(a){var b=a.sPaginationType,c=u.ext.pager[b],d="function"===typeof c,e=function(g){fa(g)};b=k("
").addClass(a.oClasses.sPaging+b)[0];var f=a.aanFeatures;d||c.fnInit(a,b,e);f.p||(b.id=a.sTableId+"_paginate",a.aoDrawCallback.push({fn:function(g){if(d){var h=g._iDisplayStart,l=g._iDisplayLength, 68 | n=g.fnRecordsDisplay(),m=-1===l;h=m?0:Math.ceil(h/l);l=m?1:Math.ceil(n/l);n=c(h,l);var p;m=0;for(p=f.p.length;mf&&(d=0)):"first"==b?d=0:"previous"==b?(d=0<=e?d-e:0,0>d&&(d=0)):"next"==b?d+e",{id:a.aanFeatures.r?null:a.sTableId+"_processing","class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]}function U(a,b){a.oFeatures.bProcessing&&k(a.aanFeatures.r).css("display",b?"block":"none");H(a,null,"processing",[a,b])}function Mb(a){var b=k(a.nTable);b.attr("role","grid");var c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var d=c.sX, 70 | e=c.sY,f=a.oClasses,g=b.children("caption"),h=g.length?g[0]._captionSide:null,l=k(b[0].cloneNode(!1)),n=k(b[0].cloneNode(!1)),m=b.children("tfoot");m.length||(m=null);l=k("
",{"class":f.sScrollWrapper}).append(k("
",{"class":f.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:d?d?K(d):null:"100%"}).append(k("
",{"class":f.sScrollHeadInner}).css({"box-sizing":"content-box",width:c.sXInner||"100%"}).append(l.removeAttr("id").css("margin-left",0).append("top"===h? 71 | g:null).append(b.children("thead"))))).append(k("
",{"class":f.sScrollBody}).css({position:"relative",overflow:"auto",width:d?K(d):null}).append(b));m&&l.append(k("
",{"class":f.sScrollFoot}).css({overflow:"hidden",border:0,width:d?d?K(d):null:"100%"}).append(k("
",{"class":f.sScrollFootInner}).append(n.removeAttr("id").css("margin-left",0).append("bottom"===h?g:null).append(b.children("tfoot")))));b=l.children();var p=b[0];f=b[1];var t=m?b[2]:null;if(d)k(f).on("scroll.DT",function(v){v= 72 | this.scrollLeft;p.scrollLeft=v;m&&(t.scrollLeft=v)});k(f).css("max-height",e);c.bCollapse||k(f).css("height",e);a.nScrollHead=p;a.nScrollBody=f;a.nScrollFoot=t;a.aoDrawCallback.push({fn:Fa,sName:"scrolling"});return l[0]}function Fa(a){var b=a.oScroll,c=b.sX,d=b.sXInner,e=b.sY;b=b.iBarWidth;var f=k(a.nScrollHead),g=f[0].style,h=f.children("div"),l=h[0].style,n=h.children("table");h=a.nScrollBody;var m=k(h),p=h.style,t=k(a.nScrollFoot).children("div"),v=t.children("table"),x=k(a.nTHead),r=k(a.nTable), 73 | A=r[0],D=A.style,I=a.nTFoot?k(a.nTFoot):null,W=a.oBrowser,M=W.bScrollOversize,B=T(a.aoColumns,"nTh"),E=[],aa=[],X=[],Aa=[],mb,Ba=function(F){F=F.style;F.paddingTop="0";F.paddingBottom="0";F.borderTopWidth="0";F.borderBottomWidth="0";F.height=0};var ha=h.scrollHeight>h.clientHeight;if(a.scrollBarVis!==ha&&a.scrollBarVis!==q)a.scrollBarVis=ha,ra(a);else{a.scrollBarVis=ha;r.children("thead, tfoot").remove();if(I){var ka=I.clone().prependTo(r);var la=I.find("tr");ka=ka.find("tr")}var nb=x.clone().prependTo(r); 74 | x=x.find("tr");ha=nb.find("tr");nb.find("th, td").removeAttr("tabindex");c||(p.width="100%",f[0].style.width="100%");k.each(La(a,nb),function(F,Y){mb=sa(a,F);Y.style.width=a.aoColumns[mb].sWidth});I&&ba(function(F){F.style.width=""},ka);f=r.outerWidth();""===c?(D.width="100%",M&&(r.find("tbody").height()>h.offsetHeight||"scroll"==m.css("overflow-y"))&&(D.width=K(r.outerWidth()-b)),f=r.outerWidth()):""!==d&&(D.width=K(d),f=r.outerWidth());ba(Ba,ha);ba(function(F){X.push(F.innerHTML);E.push(K(k(F).css("width")))}, 75 | ha);ba(function(F,Y){-1!==k.inArray(F,B)&&(F.style.width=E[Y])},x);k(ha).height(0);I&&(ba(Ba,ka),ba(function(F){Aa.push(F.innerHTML);aa.push(K(k(F).css("width")))},ka),ba(function(F,Y){F.style.width=aa[Y]},la),k(ka).height(0));ba(function(F,Y){F.innerHTML='
'+X[Y]+"
";F.childNodes[0].style.height="0";F.childNodes[0].style.overflow="hidden";F.style.width=E[Y]},ha);I&&ba(function(F,Y){F.innerHTML='
'+Aa[Y]+"
";F.childNodes[0].style.height= 76 | "0";F.childNodes[0].style.overflow="hidden";F.style.width=aa[Y]},ka);r.outerWidth()h.offsetHeight||"scroll"==m.css("overflow-y")?f+b:f,M&&(h.scrollHeight>h.offsetHeight||"scroll"==m.css("overflow-y"))&&(D.width=K(la-b)),""!==c&&""===d||ca(a,1,"Possible column misalignment",6)):la="100%";p.width=K(la);g.width=K(la);I&&(a.nScrollFoot.style.width=K(la));!e&&M&&(p.height=K(A.offsetHeight+b));c=r.outerWidth();n[0].style.width=K(c);l.width=K(c);d=r.height()>h.clientHeight||"scroll"== 77 | m.css("overflow-y");e="padding"+(W.bScrollbarLeft?"Left":"Right");l[e]=d?b+"px":"0px";I&&(v[0].style.width=K(c),t[0].style.width=K(c),t[0].style[e]=d?b+"px":"0px");r.children("colgroup").insertBefore(r.children("thead"));m.trigger("scroll");!a.bSorted&&!a.bFiltered||a._drawHold||(h.scrollTop=0)}}function ba(a,b,c){for(var d=0,e=0,f=b.length,g,h;e").appendTo(h.find("tbody"));h.find("thead, tfoot").remove();h.append(k(a.nTHead).clone()).append(k(a.nTFoot).clone());h.find("tfoot th, tfoot td").css("width","");n=La(a,h.find("thead")[0]);for(v=0;v").css({width:r.sWidthOrig,margin:0,padding:0,border:0,height:1}));if(a.aoData.length)for(v=0;v").css(f||e?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(h).appendTo(p);f&&g?h.width(g):f?(h.css("width","auto"),h.removeAttr("width"),h.width()").css("width",K(a)).appendTo(b||z.body);b=a[0].offsetWidth;a.remove();return b}function $b(a,b){var c=ac(a,b);if(0>c)return null;var d=a.aoData[c];return d.nTr?d.anCells[b]:k("").html(S(a,c,b,"display"))[0]}function ac(a,b){for(var c,d=-1,e=-1,f=0,g=a.aoData.length;fd&&(d=c.length,e=f);return e}function K(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function pa(a){var b=[],c=a.aoColumns;var d=a.aaSortingFixed;var e=k.isPlainObject(d);var f=[];var g=function(m){m.length&&!Array.isArray(m[0])?f.push(m):k.merge(f,m)};Array.isArray(d)&&g(d);e&&d.pre&&g(d.pre);g(a.aaSorting);e&&d.post&&g(d.post);for(a=0;aI?1:0;if(0!==D)return"asc"===A.dir?D:-D}D=c[m];I=c[p];return DI?1:0}):g.sort(function(m,p){var t,v=h.length,x=e[m]._aSortData,r=e[p]._aSortData;for(t=0;tI?1:0})}a.bSorted=!0}function cc(a){var b=a.aoColumns,c=pa(a);a=a.oLanguage.oAria;for(var d=0,e=b.length;d/g,"");var l=f.nTh;l.removeAttribute("aria-sort");f.bSortable&&(0e?e+1:3))}e=0;for(f=d.length;ee?e+1:3))}a.aLastSort=d}function bc(a,b){var c=a.aoColumns[b],d=u.ext.order[c.sSortDataType],e;d&&(e=d.call(a.oInstance,a,b,ta(a,b)));for(var f,g=u.ext.type.order[c.sType+ 88 | "-pre"],h=0,l=a.aoData.length;h= 90 | f.length?[0,m[1]]:m)}));h.search!==q&&k.extend(a.oPreviousSearch,Wb(h.search));if(h.columns)for(d=0,e=h.columns.length;d=c&&(b=c-d);b-=b%d;if(-1===d||0>b)b=0;a._iDisplayStart=b}function fb(a,b){a=a.renderer;var c=u.ext.renderer[b];return k.isPlainObject(a)&&a[b]?c[a[b]]||c._:"string"===typeof a?c[a]||c._:c._}function P(a){return a.oFeatures.bServerSide?"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function Ca(a,b){var c=ec.numbers_length,d=Math.floor(c/2); 94 | b<=c?a=qa(0,b):a<=d?(a=qa(0,c-2),a.push("ellipsis"),a.push(b-1)):(a>=b-1-d?a=qa(b-(c-2),b):(a=qa(a-d+2,a+d-1),a.push("ellipsis"),a.push(b-1)),a.splice(0,0,"ellipsis"),a.splice(0,0,0));a.DT_el="span";return a}function Wa(a){k.each({num:function(b){return Ta(b,a)},"num-fmt":function(b){return Ta(b,a,rb)},"html-num":function(b){return Ta(b,a,Ua)},"html-num-fmt":function(b){return Ta(b,a,Ua,rb)}},function(b,c){L.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(L.type.search[b+a]=L.type.search.html)})}function fc(a){return function(){var b= 95 | [Sa(this[u.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return u.ext.internal[a].apply(this,b)}}var u=function(a){this.$=function(f,g){return this.api(!0).$(f,g)};this._=function(f,g){return this.api(!0).rows(f,g).data()};this.api=function(f){return f?new C(Sa(this[L.iApiIndex])):new C(this)};this.fnAddData=function(f,g){var h=this.api(!0);f=Array.isArray(f)&&(Array.isArray(f[0])||k.isPlainObject(f[0]))?h.rows.add(f):h.row.add(f);(g===q||g)&&h.draw();return f.flatten().toArray()}; 96 | this.fnAdjustColumnSizing=function(f){var g=this.api(!0).columns.adjust(),h=g.settings()[0],l=h.oScroll;f===q||f?g.draw(!1):(""!==l.sX||""!==l.sY)&&Fa(h)};this.fnClearTable=function(f){var g=this.api(!0).clear();(f===q||f)&&g.draw()};this.fnClose=function(f){this.api(!0).row(f).child.hide()};this.fnDeleteRow=function(f,g,h){var l=this.api(!0);f=l.rows(f);var n=f.settings()[0],m=n.aoData[f[0][0]];f.remove();g&&g.call(this,n,m);(h===q||h)&&l.draw();return m};this.fnDestroy=function(f){this.api(!0).destroy(f)}; 97 | this.fnDraw=function(f){this.api(!0).draw(f)};this.fnFilter=function(f,g,h,l,n,m){n=this.api(!0);null===g||g===q?n.search(f,h,l,m):n.column(g).search(f,h,l,m);n.draw()};this.fnGetData=function(f,g){var h=this.api(!0);if(f!==q){var l=f.nodeName?f.nodeName.toLowerCase():"";return g!==q||"td"==l||"th"==l?h.cell(f,g).data():h.row(f).data()||null}return h.data().toArray()};this.fnGetNodes=function(f){var g=this.api(!0);return f!==q?g.row(f).node():g.rows().nodes().flatten().toArray()};this.fnGetPosition= 98 | function(f){var g=this.api(!0),h=f.nodeName.toUpperCase();return"TR"==h?g.row(f).index():"TD"==h||"TH"==h?(f=g.cell(f).index(),[f.row,f.columnVisible,f.column]):null};this.fnIsOpen=function(f){return this.api(!0).row(f).child.isShown()};this.fnOpen=function(f,g,h){return this.api(!0).row(f).child(g,h).show().child()[0]};this.fnPageChange=function(f,g){f=this.api(!0).page(f);(g===q||g)&&f.draw(!1)};this.fnSetColumnVis=function(f,g,h){f=this.api(!0).column(f).visible(g);(h===q||h)&&f.columns.adjust().draw()}; 99 | this.fnSettings=function(){return Sa(this[L.iApiIndex])};this.fnSort=function(f){this.api(!0).order(f).draw()};this.fnSortListener=function(f,g,h){this.api(!0).order.listener(f,g,h)};this.fnUpdate=function(f,g,h,l,n){var m=this.api(!0);h===q||null===h?m.row(g).data(f):m.cell(g,h).data(f);(n===q||n)&&m.columns.adjust();(l===q||l)&&m.draw();return 0};this.fnVersionCheck=L.fnVersionCheck;var b=this,c=a===q,d=this.length;c&&(a={});this.oApi=this.internal=L.internal;for(var e in u.ext.internal)e&&(this[e]= 100 | fc(e));this.each(function(){var f={},g=1").appendTo(p));r.nTHead=E[0];var aa=p.children("tbody");0===aa.length&&(aa=k("").insertAfter(E));r.nTBody=aa[0];E=p.children("tfoot"); 109 | 0===E.length&&0").appendTo(p));0===E.length||0===E.children().length?p.addClass(A.sNoFooter):0/g,tc=/^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/,uc=/(\/|\.|\*|\+|\?|\||\(|\)|\[|\]|\{|\}|\\|\$|\^|\-)/g,rb=/['\u00A0,$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfkɃΞ]/gi,Z=function(a){return a&&!0!==a&&"-"!==a?!1:!0},hc=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},ic=function(a,b){sb[b]||(sb[b]=new RegExp(ib(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g,"").replace(sb[b],"."): 111 | a},tb=function(a,b,c){var d="string"===typeof a;if(Z(a))return!0;b&&d&&(a=ic(a,b));c&&d&&(a=a.replace(rb,""));return!isNaN(parseFloat(a))&&isFinite(a)},jc=function(a,b,c){return Z(a)?!0:Z(a)||"string"===typeof a?tb(a.replace(Ua,""),b,c)?!0:null:null},T=function(a,b,c){var d=[],e=0,f=a.length;if(c!==q)for(;ea.length)){var b=a.slice().sort();for(var c=b[0],d=1,e=b.length;d")[0],rc=Pa.textContent!==q,sc=/<.*?>/g,gb=u.util.throttle,mc=[],N=Array.prototype,vc=function(a){var b,c=u.settings,d=k.map(c,function(f,g){return f.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase()){var e=k.inArray(a,d);return-1!==e?[c[e]]:null}if(a&&"function"===typeof a.settings)return a.settings().toArray(); 115 | "string"===typeof a?b=k(a):a instanceof k&&(b=a)}else return[];if(b)return b.map(function(f){e=k.inArray(this,d);return-1!==e?c[e]:null}).toArray()};var C=function(a,b){if(!(this instanceof C))return new C(a,b);var c=[],d=function(g){(g=vc(g))&&c.push.apply(c,g)};if(Array.isArray(a))for(var e=0,f=a.length;ea?new C(b[a],this[a]):null},filter:function(a){var b=[];if(N.filter)b=N.filter.call(this,a,this);else for(var c=0,d=this.length;c").addClass(h),k("td",l).addClass(h).html(g)[0].colSpan=na(a),e.push(l[0]))};f(c,d);b._details&&b._details.detach();b._details=k(e);b._detailsShow&&b._details.insertAfter(b.nTr)},xb=function(a,b){var c=a.context;c.length&& 137 | (a=c[0].aoData[b!==q?b:a[0]])&&a._details&&(a._details.remove(),a._detailsShow=q,a._details=q)},pc=function(a,b){var c=a.context;c.length&&a.length&&(a=c[0].aoData[a[0]],a._details&&((a._detailsShow=b)?a._details.insertAfter(a.nTr):a._details.detach(),yc(c[0])))},yc=function(a){var b=new C(a),c=a.aoData;b.off("draw.dt.DT_details column-visibility.dt.DT_details destroy.dt.DT_details");0h){var m=k.map(d,function(p, 141 | t){return p.bVisible?t:null});return[m[m.length+h]]}return[sa(a,h)];case "name":return k.map(e,function(p,t){return p===n[1]?t:null});default:return[]}if(g.nodeName&&g._DT_CellIndex)return[g._DT_CellIndex.column];h=k(f).filter(g).map(function(){return k.inArray(this,f)}).toArray();if(h.length||!g.nodeName)return h;h=k(g).closest("*[data-dt-column]");return h.length?[h.data("dt-column")]:[]},a,c)};w("columns()",function(a,b){a===q?a="":k.isPlainObject(a)&&(b=a,a="");b=vb(b);var c=this.iterator("table", 142 | function(d){return Ac(d,a,b)},1);c.selector.cols=a;c.selector.opts=b;return c});J("columns().header()","column().header()",function(a,b){return this.iterator("column",function(c,d){return c.aoColumns[d].nTh},1)});J("columns().footer()","column().footer()",function(a,b){return this.iterator("column",function(c,d){return c.aoColumns[d].nTf},1)});J("columns().data()","column().data()",function(){return this.iterator("column-rows",qc,1)});J("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column", 143 | function(a,b){return a.aoColumns[b].mData},1)});J("columns().cache()","column().cache()",function(a){return this.iterator("column-rows",function(b,c,d,e,f){return Da(b.aoData,f,"search"===a?"_aFilterData":"_aSortData",c)},1)});J("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",function(a,b,c,d,e){return Da(a.aoData,e,"anCells",b)},1)});J("columns().visible()","column().visible()",function(a,b){var c=this,d=this.iterator("column",function(e,f){if(a===q)return e.aoColumns[f].bVisible; 144 | var g=e.aoColumns,h=g[f],l=e.aoData,n;if(a!==q&&h.bVisible!==a){if(a){var m=k.inArray(!0,T(g,"bVisible"),f+1);g=0;for(n=l.length;gd;return!0};u.isDataTable=u.fnIsDataTable=function(a){var b=k(a).get(0),c=!1;if(a instanceof u.Api)return!0;k.each(u.settings,function(d,e){d=e.nScrollHead?k("table",e.nScrollHead)[0]:null;var f=e.nScrollFoot?k("table",e.nScrollFoot)[0]:null;if(e.nTable===b||d===b||f===b)c=!0});return c};u.tables=u.fnTables=function(a){var b=!1;k.isPlainObject(a)&&(b=a.api,a=a.visible); 155 | var c=k.map(u.settings,function(d){if(!a||a&&k(d.nTable).is(":visible"))return d.nTable});return b?new C(c):c};u.camelToHungarian=O;w("$()",function(a,b){b=this.rows(b).nodes();b=k(b);return k([].concat(b.filter(a).toArray(),b.find(a).toArray()))});k.each(["on","one","off"],function(a,b){w(b+"()",function(){var c=Array.prototype.slice.call(arguments);c[0]=k.map(c[0].split(/\s/),function(e){return e.match(/\.dt\b/)?e:e+".dt"}).join(" ");var d=k(this.tables().nodes());d[b].apply(d,c);return this})}); 156 | w("clear()",function(){return this.iterator("table",function(a){Ia(a)})});w("settings()",function(){return new C(this.context,this.context)});w("init()",function(){var a=this.context;return a.length?a[0].oInit:null});w("data()",function(){return this.iterator("table",function(a){return T(a.aoData,"_aData")}).flatten()});w("destroy()",function(a){a=a||!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode,d=b.oClasses,e=b.nTable,f=b.nTBody,g=b.nTHead,h=b.nTFoot,l=k(e);f=k(f); 157 | var n=k(b.nTableWrapper),m=k.map(b.aoData,function(t){return t.nTr}),p;b.bDestroying=!0;H(b,"aoDestroyCallback","destroy",[b]);a||(new C(b)).columns().visible(!0);n.off(".DT").find(":not(tbody *)").off(".DT");k(y).off(".DT-"+b.sInstance);e!=g.parentNode&&(l.children("thead").detach(),l.append(g));h&&e!=h.parentNode&&(l.children("tfoot").detach(),l.append(h));b.aaSorting=[];b.aaSortingFixed=[];Qa(b);k(m).removeClass(b.asStripeClasses.join(" "));k("th, td",g).removeClass(d.sSortable+" "+d.sSortableAsc+ 158 | " "+d.sSortableDesc+" "+d.sSortableNone);f.children().detach();f.append(m);g=a?"remove":"detach";l[g]();n[g]();!a&&c&&(c.insertBefore(e,b.nTableReinsertBefore),l.css("width",b.sDestroyWidth).removeClass(d.sTable),(p=b.asDestroyStripes.length)&&f.children().each(function(t){k(this).addClass(b.asDestroyStripes[t%p])}));c=k.inArray(b,u.settings);-1!==c&&u.settings.splice(c,1)})});k.each(["column","row","cell"],function(a,b){w(b+"s().every()",function(c){var d=this.selector.opts,e=this;return this.iterator(b, 159 | function(f,g,h,l,n){c.call(e[b](g,"cell"===b?h:d,"cell"===b?d:q),g,h,l,n)})})});w("i18n()",function(a,b,c){var d=this.context[0];a=ia(a)(d.oLanguage);a===q&&(a=b);c!==q&&k.isPlainObject(a)&&(a=a[c]!==q?a[c]:a._);return a.replace("%d",c)});u.version="1.10.25";u.settings=[];u.models={};u.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0};u.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null,idx:-1};u.models.oColumn= 160 | {idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,sWidth:null,sWidthOrig:null};u.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null, 161 | aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null, 162 | fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){return{}}},fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+"_"+location.pathname,JSON.stringify(b))}catch(c){}},fnStateSaveParams:null, 163 | iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sDecimal:"", 164 | sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:k.extend({},u.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",renderer:null,rowId:"DT_RowId"};G(u.defaults);u.defaults.column={aDataSort:null,iDataSort:-1,asSorting:["asc", 165 | "desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};G(u.defaults.column);u.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,iBarWidth:0,sX:null, 166 | sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1,bBounding:!1,barWidth:0},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aIds:{},aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[], 167 | aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,jqXHR:null,json:q,oAjaxData:q,fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1, 168 | iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==P(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==P(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a=this._iDisplayLength,b=this._iDisplayStart,c=b+a,d=this.aiDisplay.length,e=this.oFeatures,f=e.bPaginate; 169 | return e.bServerSide?!1===f||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!f||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};u.ext=L={buttons:{},classes:{},builder:"-source-",errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:u.fnVersionCheck, 170 | iApiIndex:0,oJUIClasses:{},sVersion:u.version};k.extend(L,{afnFiltering:L.search,aTypes:L.type.detect,ofnSearch:L.type.search,oSort:L.type.order,afnSortData:L.order,aoFeatures:L.feature,oApi:L.internal,oStdClasses:L.classes,oPagination:L.pager});k.extend(u.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter", 171 | sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_desc_disabled",sSortableDesc:"sorting_asc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody", 172 | sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var ec=u.ext.pager;k.extend(ec,{simple:function(a,b){return["previous","next"]},full:function(a,b){return["first","previous","next","last"]},numbers:function(a,b){return[Ca(a,b)]},simple_numbers:function(a,b){return["previous",Ca(a,b),"next"]}, 173 | full_numbers:function(a,b){return["first","previous",Ca(a,b),"next","last"]},first_last_numbers:function(a,b){return["first",Ca(a,b),"last"]},_numbers:Ca,numbers_length:7});k.extend(!0,u.ext.renderer,{pageButton:{_:function(a,b,c,d,e,f){var g=a.oClasses,h=a.oLanguage.oPaginate,l=a.oLanguage.oAria.paginate||{},n,m,p=0,t=function(x,r){var A,D=g.sPageButtonDisabled,I=function(E){lb(a,E.data.action,!0)};var W=0;for(A=r.length;W").appendTo(x); 174 | t(B,M)}else{n=null;m=M;B=a.iTabIndex;switch(M){case "ellipsis":x.append('');break;case "first":n=h.sFirst;0===e&&(B=-1,m+=" "+D);break;case "previous":n=h.sPrevious;0===e&&(B=-1,m+=" "+D);break;case "next":n=h.sNext;if(0===f||e===f-1)B=-1,m+=" "+D;break;case "last":n=h.sLast;if(0===f||e===f-1)B=-1,m+=" "+D;break;default:n=a.fnFormatNumber(M+1),m=e===M?g.sPageButtonActive:""}null!==n&&(B=k("",{"class":g.sPageButton+" "+m,"aria-controls":a.sTableId,"aria-label":l[M], 175 | "data-dt-idx":p,tabindex:B,id:0===c&&"string"===typeof M?a.sTableId+"_"+M:null}).html(n).appendTo(x),pb(B,{action:M},I),p++)}}};try{var v=k(b).find(z.activeElement).data("dt-idx")}catch(x){}t(k(b).empty(),d);v!==q&&k(b).find("[data-dt-idx="+v+"]").trigger("focus")}}});k.extend(u.ext.type.detect,[function(a,b){b=b.oLanguage.sDecimal;return tb(a,b)?"num"+b:null},function(a,b){if(a&&!(a instanceof Date)&&!tc.test(a))return null;b=Date.parse(a);return null!==b&&!isNaN(b)||Z(a)?"date":null},function(a, 176 | b){b=b.oLanguage.sDecimal;return tb(a,b,!0)?"num-fmt"+b:null},function(a,b){b=b.oLanguage.sDecimal;return jc(a,b)?"html-num"+b:null},function(a,b){b=b.oLanguage.sDecimal;return jc(a,b,!0)?"html-num-fmt"+b:null},function(a,b){return Z(a)||"string"===typeof a&&-1!==a.indexOf("<")?"html":null}]);k.extend(u.ext.type.search,{html:function(a){return Z(a)?a:"string"===typeof a?a.replace(gc," ").replace(Ua,""):""},string:function(a){return Z(a)?a:"string"===typeof a?a.replace(gc," "):a}});var Ta=function(a, 177 | b,c,d){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=ic(a,b));a.replace&&(c&&(a=a.replace(c,"")),d&&(a=a.replace(d,"")));return 1*a};k.extend(L.type.order,{"date-pre":function(a){a=Date.parse(a);return isNaN(a)?-Infinity:a},"html-pre":function(a){return Z(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return Z(a)?"":"string"===typeof a?a.toLowerCase():a.toString?a.toString():""},"string-asc":function(a,b){return ab?1:0},"string-desc":function(a,b){return a< 178 | b?1:a>b?-1:0}});Wa("");k.extend(!0,u.ext.renderer,{header:{_:function(a,b,c,d){k(a.nTable).on("order.dt.DT",function(e,f,g,h){a===f&&(e=c.idx,b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass("asc"==h[e]?d.sSortAsc:"desc"==h[e]?d.sSortDesc:c.sSortingClass))})},jqueryui:function(a,b,c,d){k("
").addClass(d.sSortJUIWrapper).append(b.contents()).append(k("").addClass(d.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);k(a.nTable).on("order.dt.DT",function(e,f,g,h){a===f&&(e=c.idx,b.removeClass(d.sSortAsc+ 179 | " "+d.sSortDesc).addClass("asc"==h[e]?d.sSortAsc:"desc"==h[e]?d.sSortDesc:c.sSortingClass),b.find("span."+d.sSortIcon).removeClass(d.sSortJUIAsc+" "+d.sSortJUIDesc+" "+d.sSortJUI+" "+d.sSortJUIAscAllowed+" "+d.sSortJUIDescAllowed).addClass("asc"==h[e]?d.sSortJUIAsc:"desc"==h[e]?d.sSortJUIDesc:c.sSortingClassJUI))})}}});var yb=function(a){return"string"===typeof a?a.replace(/&/g,"&").replace(//g,">").replace(/"/g,"""):a};u.render={number:function(a,b,c,d,e){return{display:function(f){if("number"!== 180 | typeof f&&"string"!==typeof f)return f;var g=0>f?"-":"",h=parseFloat(f);if(isNaN(h))return yb(f);h=h.toFixed(c);f=Math.abs(h);h=parseInt(f,10);f=c?b+(f-h).toFixed(c).substring(2):"";0===h&&0===parseFloat(f)&&(g="");return g+(d||"")+h.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+f+(e||"")}}},text:function(){return{display:yb,filter:yb}}};k.extend(u.ext.internal,{_fnExternApiFunc:fc,_fnBuildAjax:Ma,_fnAjaxUpdate:Gb,_fnAjaxParameters:Pb,_fnAjaxUpdateDraw:Qb,_fnAjaxDataSrc:Na,_fnAddColumn:Xa,_fnColumnOptions:Ea, 181 | _fnAdjustColumnSizing:ra,_fnVisibleToColumnIndex:sa,_fnColumnIndexToVisible:ta,_fnVisbleColumns:na,_fnGetColumns:Ga,_fnColumnTypes:Za,_fnApplyColumnDefs:Db,_fnHungarianMap:G,_fnCamelToHungarian:O,_fnLanguageCompat:ma,_fnBrowserDetect:Bb,_fnAddData:ea,_fnAddTr:Ha,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==q?b._DT_RowIndex:null},_fnNodeToColumnIndex:function(a,b,c){return k.inArray(c,a.aoData[b].anCells)},_fnGetCellData:S,_fnSetCellData:Eb,_fnSplitObjNotation:bb,_fnGetObjectDataFn:ia, 182 | _fnSetObjectDataFn:da,_fnGetDataMaster:cb,_fnClearTable:Ia,_fnDeleteIndex:Ja,_fnInvalidate:va,_fnGetRowElements:ab,_fnCreateTr:$a,_fnBuildHead:Fb,_fnDrawHead:xa,_fnDraw:fa,_fnReDraw:ja,_fnAddOptionsHtml:Ib,_fnDetectHeader:wa,_fnGetUniqueThs:La,_fnFeatureHtmlFilter:Kb,_fnFilterComplete:ya,_fnFilterCustom:Tb,_fnFilterColumn:Sb,_fnFilter:Rb,_fnFilterCreateSearch:hb,_fnEscapeRegex:ib,_fnFilterData:Ub,_fnFeatureHtmlInfo:Nb,_fnUpdateInfo:Xb,_fnInfoMacros:Yb,_fnInitialise:za,_fnInitComplete:Oa,_fnLengthChange:jb, 183 | _fnFeatureHtmlLength:Jb,_fnFeatureHtmlPaginate:Ob,_fnPageChange:lb,_fnFeatureHtmlProcessing:Lb,_fnProcessingDisplay:U,_fnFeatureHtmlTable:Mb,_fnScrollDraw:Fa,_fnApplyToChildren:ba,_fnCalculateColumnWidths:Ya,_fnThrottle:gb,_fnConvertToWidth:Zb,_fnGetWidestNode:$b,_fnGetMaxLenString:ac,_fnStringToCss:K,_fnSortFlatten:pa,_fnSort:Hb,_fnSortAria:cc,_fnSortListener:ob,_fnSortAttachListener:eb,_fnSortingClasses:Qa,_fnSortData:bc,_fnSaveState:Ra,_fnLoadState:dc,_fnSettingsFromNode:Sa,_fnLog:ca,_fnMap:V, 184 | _fnBindAction:pb,_fnCallbackReg:Q,_fnCallbackFire:H,_fnLengthOverflow:kb,_fnRenderer:fb,_fnDataSource:P,_fnRowAttributes:db,_fnExtend:qb,_fnCalculateEnd:function(){}});k.fn.dataTable=u;u.$=k;k.fn.dataTableSettings=u.settings;k.fn.dataTableExt=u.ext;k.fn.DataTable=function(a){return k(this).dataTable(a).api()};k.each(u,function(a,b){k.fn.DataTable[a]=b});return k.fn.dataTable}); 185 | -------------------------------------------------------------------------------- /assets/style.css: -------------------------------------------------------------------------------- 1 | .source { 2 | table-layout: fixed; 3 | width: 100%; 4 | white-space: nowrap; 5 | } 6 | .source td { 7 | white-space: nowrap; 8 | overflow: hidden; 9 | text-overflow: ellipsis; 10 | } 11 | .red { 12 | background-color: #ffd0d0; 13 | } 14 | .cyan { 15 | background-color: cyan; 16 | } 17 | body { 18 | font-family: -apple-system, sans-serif; 19 | } 20 | pre { 21 | margin-top: 0px !important; 22 | margin-bottom: 0px !important; 23 | } 24 | .source-name-title { 25 | padding: 5px 10px; 26 | border-bottom: 1px solid #dbdbdb; 27 | background-color: #eee; 28 | line-height: 35px; 29 | } 30 | .centered { 31 | display: table; 32 | margin-left: left; 33 | margin-right: auto; 34 | border: 1px solid #dbdbdb; 35 | border-radius: 3px; 36 | } 37 | .expansion-view { 38 | background-color: rgba(0, 0, 0, 0); 39 | margin-left: 0px; 40 | margin-top: 5px; 41 | margin-right: 5px; 42 | margin-bottom: 5px; 43 | border: 1px solid #dbdbdb; 44 | border-radius: 3px; 45 | } 46 | table { 47 | border-collapse: collapse; 48 | } 49 | .light-row { 50 | background: #ffffff; 51 | border: 1px solid #dbdbdb; 52 | } 53 | .column-entry { 54 | text-align: right; 55 | } 56 | .column-entry-left { 57 | text-align: left; 58 | } 59 | .column-entry-white { 60 | text-align: right; 61 | background-color: #ffffff; 62 | } 63 | .column-entry-red { 64 | text-align: right; 65 | background-color: #ffd0d0; 66 | } 67 | .column-entry-green { 68 | text-align: right; 69 | background-color: #d0ffd0; 70 | } 71 | .column-entry-yellow { 72 | text-align: left; 73 | background-color: #ffe1a6; 74 | } 75 | .column-entry-0 { 76 | background-color: #ffffff; 77 | } 78 | .column-entry-1 { 79 | background-color: #eeeeee; 80 | } 81 | .line-number { 82 | text-align: right; 83 | color: #aaa; 84 | } 85 | .covered-line { 86 | text-align: right; 87 | color: #0080ff; 88 | } 89 | .uncovered-line { 90 | text-align: right; 91 | color: #ff3300; 92 | } 93 | .tooltip { 94 | position: relative; 95 | display: inline; 96 | background-color: #b3e6ff; 97 | text-decoration: none; 98 | } 99 | .tooltip span.tooltip-content { 100 | position: absolute; 101 | width: 100px; 102 | margin-left: -50px; 103 | color: #FFFFFF; 104 | background: #000000; 105 | height: 30px; 106 | line-height: 30px; 107 | text-align: center; 108 | visibility: hidden; 109 | border-radius: 6px; 110 | } 111 | .tooltip span.tooltip-content:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | left: 50%; 116 | margin-left: -8px; 117 | width: 0; height: 0; 118 | border-top: 8px solid #000000; 119 | border-right: 8px solid transparent; 120 | border-left: 8px solid transparent; 121 | } 122 | :hover.tooltip span.tooltip-content { 123 | visibility: visible; 124 | opacity: 0.8; 125 | bottom: 30px; 126 | left: 50%; 127 | z-index: 999; 128 | } 129 | th, td { 130 | vertical-align: top; 131 | padding: 2px 5px; 132 | border-collapse: collapse; 133 | border-right: solid 1px #eee; 134 | border-left: solid 1px #eee; 135 | } 136 | td:first-child { 137 | border-left: none; 138 | } 139 | td:last-child { 140 | border-right: none; 141 | } 142 | .expanded { 143 | background-color: #f2f2f2; 144 | padding-top: 5px; 145 | padding-left: 5px; 146 | } 147 | .col-left { 148 | float: left; 149 | margin-bottom: -99999px; 150 | padding-bottom: 99999px; 151 | } 152 | 153 | /* Generated with pygmentize -S colorful -f html >> style.css */ 154 | 155 | .hll { background-color: #ffffcc } 156 | .c { color: #888888 } /* Comment */ 157 | .err { color: #FF0000; background-color: #FFAAAA } /* Error */ 158 | .k { color: #008800; font-weight: bold } /* Keyword */ 159 | .o { color: #333333 } /* Operator */ 160 | .ch { color: #888888 } /* Comment.Hashbang */ 161 | .cm { color: #888888 } /* Comment.Multiline */ 162 | .cp { color: #557799 } /* Comment.Preproc */ 163 | .cpf { color: #888888 } /* Comment.PreprocFile */ 164 | .c1 { color: #888888 } /* Comment.Single */ 165 | .cs { color: #cc0000; font-weight: bold } /* Comment.Special */ 166 | .gd { color: #A00000 } /* Generic.Deleted */ 167 | .ge { font-style: italic } /* Generic.Emph */ 168 | .gr { color: #FF0000 } /* Generic.Error */ 169 | .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 170 | .gi { color: #00A000 } /* Generic.Inserted */ 171 | .go { color: #888888 } /* Generic.Output */ 172 | .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 173 | .gs { font-weight: bold } /* Generic.Strong */ 174 | .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 175 | .gt { color: #0044DD } /* Generic.Traceback */ 176 | .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ 177 | .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ 178 | .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ 179 | .kp { color: #003388; font-weight: bold } /* Keyword.Pseudo */ 180 | .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ 181 | .kt { color: #333399; font-weight: bold } /* Keyword.Type */ 182 | .m { color: #6600EE; font-weight: bold } /* Literal.Number */ 183 | .s { background-color: #fff0f0 } /* Literal.String */ 184 | .na { color: #0000CC } /* Name.Attribute */ 185 | .nb { color: #007020 } /* Name.Builtin */ 186 | .nc { color: #BB0066; font-weight: bold } /* Name.Class */ 187 | .no { color: #003366; font-weight: bold } /* Name.Constant */ 188 | .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 189 | .ni { color: #880000; font-weight: bold } /* Name.Entity */ 190 | .ne { color: #FF0000; font-weight: bold } /* Name.Exception */ 191 | .nf { color: #0066BB; font-weight: bold } /* Name.Function */ 192 | .nl { color: #997700; font-weight: bold } /* Name.Label */ 193 | .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 194 | .nt { color: #007700 } /* Name.Tag */ 195 | .nv { color: #996633 } /* Name.Variable */ 196 | .ow { color: #000000; font-weight: bold } /* Operator.Word */ 197 | .w { color: #bbbbbb } /* Text.Whitespace */ 198 | .mb { color: #6600EE; font-weight: bold } /* Literal.Number.Bin */ 199 | .mf { color: #6600EE; font-weight: bold } /* Literal.Number.Float */ 200 | .mh { color: #005588; font-weight: bold } /* Literal.Number.Hex */ 201 | .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ 202 | .mo { color: #4400EE; font-weight: bold } /* Literal.Number.Oct */ 203 | .sb { background-color: #fff0f0 } /* Literal.String.Backtick */ 204 | .sc { color: #0044DD } /* Literal.String.Char */ 205 | .sd { color: #DD4422 } /* Literal.String.Doc */ 206 | .s2 { background-color: #fff0f0 } /* Literal.String.Double */ 207 | .se { color: #666666; font-weight: bold; background-color: #fff0f0 } /* Literal.String.Escape */ 208 | .sh { background-color: #fff0f0 } /* Literal.String.Heredoc */ 209 | .si { background-color: #eeeeee } /* Literal.String.Interpol */ 210 | .sx { color: #DD2200; background-color: #fff0f0 } /* Literal.String.Other */ 211 | .sr { color: #000000; background-color: #fff0ff } /* Literal.String.Regex */ 212 | .s1 { background-color: #fff0f0 } /* Literal.String.Single */ 213 | .ss { color: #AA6600 } /* Literal.String.Symbol */ 214 | .bp { color: #007020 } /* Name.Builtin.Pseudo */ 215 | .vc { color: #336699 } /* Name.Variable.Class */ 216 | .vg { color: #dd7700; font-weight: bold } /* Name.Variable.Global */ 217 | .vi { color: #3333BB } /* Name.Variable.Instance */ 218 | .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */ 219 | 220 | table#opt_table.dataTable td { 221 | font-family: monospace; 222 | } 223 | 224 | table#opt_table_code.dataTable td { 225 | padding: 0px; 226 | border: 0px; 227 | } 228 | 229 | .back { 230 | background-color: #04AA6D; 231 | color: white; 232 | text-decoration: none; 233 | display: inline-block; 234 | padding: 8px 16px; 235 | } 236 | 237 | h1.filename-title { 238 | text-align:center; 239 | } 240 | 241 | .indent-span { 242 | -webkit-box-decoration-break: clone; 243 | box-decoration-break: clone; 244 | font-family: monospace; 245 | } 246 | 247 | table.dataTable thead th[data-is-resizable=true] { 248 | border-left: 1px solid transparent; 249 | border-right: 1px dashed #bfbfbf; 250 | } 251 | 252 | table.dataTable thead th.dt-colresizable-hover { 253 | cursor: col-resize; 254 | background-color: #eaeaea; 255 | border-left: 1px solid #bfbfbf; 256 | } 257 | 258 | table.dataTable thead th.dt-colresizable-bound-min, 259 | table.dataTable thead th.dt-colresizable-bound-max { 260 | opacity: 0.2; 261 | cursor: not-allowed !important; 262 | } -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ## Opt view config 3 | 4 | # Set this to false to disable remark_filters list 5 | use_remark_filters: true 6 | 7 | # Exclude optimization remarks with names matching this regex. 8 | # Empirical list of remark names (as of clang14): 9 | # LoopSpillReloadCopies, LoadWithLoopInvariantAddressInvalidated, MissedDetails, LoadClobbered, 10 | # NotBeneficial, SpillReloadCopies, NotPossible, NoDefinition, NeverInline, UnsupportedIrreducibleCFG, 11 | # VectorizationNotBeneficial, LoadWithLoopInvariantAddressCondExecuted, HorSLPNotBeneficial, TooCostly 12 | # LoopMayAccessStore 13 | exclude_names: NeverInline|NotPossible|LoopSpillReloadCopies|SpillReloadCopies 14 | # Exclude optimization remarks with text matching this regex: 15 | exclude_text: ^std\:\:|^__dynamic_cast|^operator new|^operator delete|^__cxa|^__clang|^__cxx 16 | 17 | # Collect all optimization remarks, not just failures 18 | collect_opt_success: False 19 | 20 | # Annotate all files, including system headers 21 | annotate_external: false 22 | -------------------------------------------------------------------------------- /config_parser.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | import re 3 | 4 | def parse(f): 5 | "Parse config file" 6 | # TODO - add scheme; currently we just take whatever is there. 7 | config = yaml.safe_load(f) 8 | if config.get('use_remark_filters', True) and 'remark_filters' in config and len(config['remark_filters']) > 0: 9 | # config wants a single regex, transform to a single expression 10 | try: 11 | regexes = [re.compile(x) for x in config['remark_filters']] 12 | except Exception as ex: 13 | raise Exception(f"Failed to parse regex in remarks_filters config. Details {ex}") 14 | config['remark_filter'] = '|'.join(x.pattern for x in regexes) 15 | 16 | return config 17 | -------------------------------------------------------------------------------- /cpp_optimization_example/.gitignore: -------------------------------------------------------------------------------- 1 | example 2 | *.o 3 | yaml_optimization_remarks 4 | .depend 5 | .vscode 6 | html_output 7 | -------------------------------------------------------------------------------- /cpp_optimization_example/Makefile: -------------------------------------------------------------------------------- 1 | CC=clang++ 2 | CXX=clang++ 3 | RM=rm -f 4 | MKDIR_P=mkdir -p 5 | OBJS_DIR=objs 6 | DEPENDS_FILE=${OBJS_DIR}/.depend 7 | YAML_OUTPUT_DIR=yaml_optimization_remarks 8 | OPTIMIZATION_RECORD_FLAGS=-fsave-optimization-record -foptimization-record-file=./${YAML_OUTPUT_DIR}/$(patsubst %.o,%.opt.yaml,$(notdir $@)) 9 | CPPFLAGS=-O3 -std=c++17 10 | LDFLAGS= 11 | LDLIBS= 12 | BINARY_NAME=example 13 | 14 | SRCS=$(wildcard *.cc) 15 | OBJS=$(addprefix $(OBJS_DIR)/,$(subst .cc,.o,$(SRCS))) 16 | 17 | .PHONY: output_folder 18 | 19 | all: output_folder ${BINARY_NAME} 20 | 21 | directories: ${YAML_OUTPUT_DIR} ${OBJS_DIR} 22 | 23 | ${YAML_OUTPUT_DIR} ${OBJS_DIR}: 24 | ${MKDIR_P} $@ 25 | 26 | ${BINARY_NAME}: $(OBJS) 27 | $(CXX) $(LDFLAGS) -o $(BINARY_NAME) $(OBJS) $(LDLIBS) 28 | 29 | ${OBJS_DIR}/%.o : %.cc 30 | $(CXX) -c $(CFLAGS) $(CPPFLAGS) $(OPTIMIZATION_RECORD_FLAGS) $< -o $@ 31 | 32 | depend: $(DEPENDS_FILE) 33 | 34 | $(DEPENDS_FILE): $(SRCS) 35 | $(RM) $(DEPENDS_FILE) 36 | $(MKDIR_P) ${YAML_OUTPUT_DIR} ${OBJS_DIR} 37 | $(CXX) $(CPPFLAGS) -MM $^ >> $(DEPENDS_FILE) 38 | 39 | include $(DEPENDS_FILE) 40 | 41 | clean: 42 | $(RM) -r ${YAML_OUTPUT_DIR} ${OBJS_DIR} 43 | 44 | -------------------------------------------------------------------------------- /cpp_optimization_example/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // Based on Roi Barkan - "Argument passing, core guidelines and concepts" - https://www.youtube.com/watch?v=uylFACqcWYI 5 | void scale_down(std::vector& v, const double& a) { 6 | for (auto& item : v) { 7 | item /= a; 8 | } 9 | } 10 | 11 | void scale_down_example() { 12 | std::vector v {2, 1, 2, 3, 4}; 13 | scale_down(v, v[0]); 14 | 15 | } 16 | 17 | int main() { 18 | scale_down_example(); 19 | 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /cpp_optimization_example/run_optview2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | cd "$(dirname "$0")" || exit 1 4 | 5 | echo "Running make..." 6 | make 7 | 8 | echo "Running optview2..." 9 | ../opt-viewer.py --open-browser --output-dir ./html_output --source-dir ./ ./yaml_optimization_remarks 10 | -------------------------------------------------------------------------------- /google39baaac72fbfa7f9.html: -------------------------------------------------------------------------------- 1 | google-site-verification: google39baaac72fbfa7f9.html -------------------------------------------------------------------------------- /opt-viewer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import functools 5 | import os.path 6 | import re 7 | import shutil 8 | import sys 9 | import json 10 | import glob 11 | import pathlib 12 | import collections 13 | from datetime import datetime 14 | from pygments import highlight 15 | from pygments.lexers.c_cpp import CppLexer 16 | from pygments.formatters import HtmlFormatter 17 | import optpmap 18 | import optrecord 19 | import config_parser 20 | import multiprocessing 21 | import platform 22 | import logging 23 | logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") 24 | 25 | desc = '''Generate HTML output to visualize optimization records from the YAML files 26 | generated with -fsave-optimization-record and -fdiagnostics-show-hotness. 27 | 28 | The tools requires PyYAML and Pygments Python packages.''' 29 | 30 | 31 | # This allows passing the global context to the child processes. 32 | class Context: 33 | def __init__(self, caller_loc = dict()): 34 | # Map function names to their source location for function where inlining happened 35 | self.caller_loc = caller_loc 36 | 37 | context = Context() 38 | 39 | 40 | def render_file_source(source_dir, output_dir, filename, line_remarks): 41 | html_filename = os.path.join(output_dir, optrecord.html_file_name(filename)) 42 | filename = filename if os.path.exists(filename) else os.path.join(source_dir, filename) 43 | 44 | html_formatter = HtmlFormatter(encoding='utf-8') 45 | cpp_lexer = CppLexer(stripnl=False) 46 | 47 | def render_source_lines(stream, line_remarks): 48 | file_text = stream.read() 49 | 50 | html_highlighted = highlight( 51 | file_text, 52 | cpp_lexer, 53 | html_formatter) 54 | 55 | # Note that the API is different between Python 2 and 3. On 56 | # Python 3, pygments.highlight() returns a bytes object, so we 57 | # have to decode. On Python 2, the output is str but since we 58 | # support unicode characters and the output streams is unicode we 59 | # decode too. 60 | html_highlighted = html_highlighted.decode('utf-8') 61 | 62 | # Take off the header and footer, these must be 63 | # reapplied line-wise, within the page structure 64 | html_highlighted = html_highlighted.replace('
', '')
 65 |         html_highlighted = html_highlighted.replace('
', '') 66 | 67 | for (linenum, html_line) in enumerate(html_highlighted.split('\n'), start=1): 68 | yield [f'
{linenum}', '', '', f'
{html_line}
', ''] 69 | 70 | cur_line_remarks = line_remarks.get(linenum, []) 71 | from collections import defaultdict 72 | d = defaultdict(list) 73 | count_deleted = defaultdict(int) 74 | for obj in cur_line_remarks: 75 | if len(d[obj.Name]) < 5: 76 | d[obj.Name].append(obj) 77 | else: 78 | count_deleted[obj.Name] += 1 79 | 80 | for obj_name, remarks in d.items(): 81 | # render caret line, if all rendered remarks share a column 82 | columns = [r.Column for r in remarks] 83 | if all(c == columns[0] for c in columns) and columns[0] != 0: 84 | yield ['', 85 | 0, 86 | {'class': f"column-entry-yellow", 'text': ''}, 87 | {'class': 'column-entry-yellow', 88 | 'text': f'''{" "*(columns[0]-1) + '^'} '''}, 89 | {'class': f"column-entry-yellow", 'text': ''}, 90 | ] 91 | for remark in remarks: 92 | yield render_inline_remark(remark, html_line) 93 | if count_deleted[obj_name] != 0: 94 | yield ['', 95 | 0, 96 | {'class': f"column-entry-yellow", 'text': ''}, 97 | {'class': 'column-entry-yellow', 'text': f'''...{count_deleted[obj_name]} similar remarks omitted. '''}, 98 | {'class': f"column-entry-yellow", 'text': ''}, 99 | ] 100 | 101 | 102 | def render_inline_remark(remark, line): 103 | inlining_context = remark.DemangledFunctionName 104 | dl = context.caller_loc.get(remark.Function) 105 | if dl: 106 | dl_dict = dict(list(dl)) 107 | link = optrecord.make_link(dl_dict['File'], dl_dict['Line'] - 2) 108 | inlining_context = f"{remark.DemangledFunctionName}" 109 | 110 | start_line = re.sub("^", "", line) 111 | spaces = len(start_line) - len(start_line.lstrip()) 112 | indent = f"{spaces + 2}ch" 113 | 114 | # Create expanded message and link if we have a multiline message. 115 | lines = remark.message.split('\n') 116 | if len(lines) > 1: 117 | expand_link = '+' 118 | message = lines[0] 119 | other_lines = "\n".join(lines[1:]) 120 | expand_message = f''' 121 | ''' 124 | else: 125 | expand_link = '' 126 | expand_message = '' 127 | message = remark.message 128 | return ['', 129 | remark.RelativeHotness, 130 | {'class': f"column-entry-{remark.color}", 'text': remark.PassWithDiffPrefix}, 131 | {'class': 'column-entry-yellow', 'text': f'''• {expand_link} {message} {expand_message}'''}, 132 | {'class': f"column-entry-yellow", 'text': inlining_context}, 133 | ] 134 | 135 | with open(html_filename, "w", encoding='utf-8') as f: 136 | if not os.path.exists(filename): 137 | f.write(f''' 138 | 139 |

Unable to locate file {filename}

140 | ''') 141 | return 142 | 143 | try: 144 | with open(filename, encoding="utf8", errors='ignore') as source_stream: 145 | entries = list(render_source_lines(source_stream, line_remarks)) 146 | except Exception: 147 | print(f"Failed to process file {filename}") 148 | raise 149 | 150 | f.write(f''' 151 | 152 | 153 | 154 | {os.path.basename(filename)} 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 |

{os.path.abspath(filename)}

163 |

Back

164 |
165 |

Back

166 | 167 | 224 | 225 | 226 | ''') 227 | 228 | def render_index(output_dir, all_remarks): 229 | def render_entry(remark): 230 | return dict(description=remark.Name, 231 | loc=f"{remark.DebugLocString}", 232 | message=remark.message, 233 | functionName=remark.DemangledFunctionName, 234 | relativeHotness=remark.RelativeHotness, 235 | color=remark.color) 236 | 237 | entries = [render_entry(remark) for remark in all_remarks] 238 | 239 | entries_summary = collections.Counter(e['description'] for e in entries) 240 | entries_summary_li = '\n'.join(f"
  • {key}: {value}" for key, value in entries_summary.items()) 241 | 242 | index_path = os.path.join(output_dir, 'index.html') 243 | with open(index_path, 'w', encoding='utf-8') as f: 244 | f.write(f''' 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | OptView2 Index 255 | 256 | 257 |

    {len(entries_summary)} issue types:

    258 |
      259 | {entries_summary_li} 260 |
    261 |
    262 |
    263 |
    264 | 289 | 290 | 291 | ''') 292 | return index_path 293 | 294 | def _render_file(source_dir, output_dir, ctx, entry): 295 | global context 296 | context = ctx 297 | filename, remarks = entry 298 | render_file_source(source_dir, output_dir, filename, remarks) 299 | 300 | 301 | def map_remarks(all_remarks): 302 | # Set up a map between function names and their source location for 303 | # function where inlining happened 304 | for remark in optrecord.itervalues(all_remarks): 305 | if isinstance(remark, optrecord.Passed) and remark.Pass == "inline" and remark.Name == "Inlined": 306 | for arg in remark.Args: 307 | arg_dict = dict(list(arg)) 308 | caller = arg_dict.get('Caller') 309 | if caller: 310 | try: 311 | context.caller_loc[caller] = arg_dict['DebugLoc'] 312 | except KeyError: 313 | pass 314 | 315 | 316 | def generate_report(all_remarks, 317 | file_remarks, 318 | source_dir, 319 | output_dir, 320 | should_display_hotness, 321 | num_jobs=1, 322 | open_browser=False): 323 | pathlib.Path(output_dir).mkdir(parents=True, exist_ok=True) 324 | 325 | logging.info('Rendering index page...') 326 | logging.info(f" {len(all_remarks):d} raw remarks") 327 | if len(all_remarks) == 0: 328 | logging.warning("Not generating report! Please verify your --source-dir argument is exactly the path from which the compiler was invoked.") 329 | return 330 | 331 | sorted_remarks = sorted(optrecord.itervalues(all_remarks), key=lambda r: (r.File, r.Line, r.Column, r.PassWithDiffPrefix)) 332 | unique_lines_remarks = [sorted_remarks[0]] 333 | for rmk in sorted_remarks: 334 | last_unq_rmk = unique_lines_remarks[-1] 335 | last_rmk_key = (last_unq_rmk.File, last_unq_rmk.Line, last_unq_rmk.Column, last_unq_rmk.PassWithDiffPrefix) 336 | rmk_key = (rmk.File, rmk.Line, rmk.Column, rmk.PassWithDiffPrefix) 337 | if rmk_key != last_rmk_key: 338 | unique_lines_remarks.append(rmk) 339 | logging.info(" {:d} unique source locations".format(len(unique_lines_remarks))) 340 | 341 | if should_display_hotness: 342 | sorted_remarks = sorted(unique_lines_remarks, key=lambda r: (r.Hotness, r.File, r.Line, r.Column, r.PassWithDiffPrefix, r.yaml_tag, r.Function), reverse=True) 343 | else: 344 | sorted_remarks = sorted(unique_lines_remarks, key=lambda r: (r.File, r.Line, r.Column, r.PassWithDiffPrefix, r.yaml_tag, r.Function)) 345 | 346 | index_path = render_index(output_dir, sorted_remarks) 347 | 348 | logging.info("Copying assets") 349 | assets_path = pathlib.Path(output_dir) / "assets" 350 | assets_path.mkdir(parents=True, exist_ok=True) 351 | for filename in glob.glob(os.path.join(str(pathlib.Path(os.path.realpath(__file__)).parent), "assets", '*.*')): 352 | shutil.copy(filename, assets_path) 353 | 354 | _render_file_bound = functools.partial(_render_file, source_dir, output_dir, context) 355 | logging.info('Rendering HTML files...') 356 | optpmap.pmap(func=_render_file_bound, 357 | iterable=file_remarks.items(), 358 | processes=num_jobs) 359 | 360 | url_path = f'file://{os.path.abspath(index_path)}' 361 | logging.info(f'Done - check the index page at {url_path}') 362 | if open_browser: 363 | try: 364 | import webbrowser 365 | if webbrowser.get("wslview %s") == None: 366 | webbrowser.open(url_path) 367 | else: 368 | webbrowser.get("wslview %s").open(url_path) 369 | except Exception: 370 | pass 371 | 372 | def main(): 373 | parser = argparse.ArgumentParser(description=desc) 374 | parser.add_argument( 375 | 'yaml_dirs_or_files', 376 | nargs='+', 377 | help='List of optimization record files or directories searched ' 378 | 'for optimization record files.') 379 | parser.add_argument( 380 | '--output-dir', 381 | '-o', 382 | default='html', 383 | help='Path to a directory where generated HTML files will be output. ' 384 | 'If the directory does not already exist, it will be created. ' 385 | '"%(default)s" by default.') 386 | parser.add_argument( 387 | '--jobs', 388 | '-j', 389 | default=None, 390 | type=int, 391 | help='Max job count (defaults to %(default)s, the current CPU count)') 392 | 393 | parser.add_argument( 394 | '--source-dir', 395 | '-s', 396 | default='', 397 | help='set source directory') 398 | 399 | parser.add_argument( 400 | '--demangler', 401 | help='Set the demangler to be used (defaults to %s)' % optrecord.Remark.default_demangler) 402 | 403 | parser.add_argument( 404 | '--exclude-name', 405 | default='', 406 | help='Omit optimization remarks with names matched by this regex') 407 | 408 | parser.add_argument( 409 | '--exclude-text', 410 | default='', 411 | help='Omit optimization remarks with names matched by this regex') 412 | 413 | parser.add_argument( 414 | '--collect-opt-success', 415 | action='store_true', 416 | help='Collect all optimization remarks, not just failures') 417 | 418 | parser.add_argument( 419 | '--annotate-external', 420 | action='store_true', 421 | help='Annotate all files, including system headers') 422 | 423 | parser.add_argument( 424 | '--open-browser', 425 | action='store_true', 426 | help='Open browser after generating HTML files') 427 | 428 | parser.add_argument( 429 | '--split-top-folders', 430 | action='store_true', 431 | help='Operate separately on every top level subfolder containing opt files - to workaround out-of-memory crashes') 432 | 433 | if platform.system() == 'Darwin': # macOs 434 | multiprocessing.set_start_method('fork') 435 | 436 | # Do not make this a global variable. Values needed to be propagated through 437 | # to individual classes and functions to be portable with multiprocessing across 438 | # Windows and non-Windows. 439 | cur_folder = pathlib.Path(__file__).parent.resolve() 440 | conf_file = os.path.join(cur_folder, "config.yaml") 441 | with open(conf_file, 'r') as config_file: 442 | config = config_parser.parse(config_file) 443 | parser.set_defaults(**config) 444 | args = parser.parse_args() 445 | 446 | source_dir = os.path.abspath(args.source_dir) 447 | 448 | if args.demangler: 449 | optrecord.Remark.set_demangler(args.demangler) 450 | 451 | start_time = datetime.now() 452 | 453 | if args.split_top_folders: 454 | subfolders = [] 455 | for item in os.listdir(args.yaml_dirs_or_files[0]): 456 | if os.path.isfile(os.path.join(args.yaml_dirs_or_files[0], item)): 457 | continue 458 | subfolders.append(item) 459 | 460 | for subfolder in subfolders: 461 | files = optrecord.find_opt_files(os.path.join(args.yaml_dirs_or_files[0], subfolder)) 462 | if not files: 463 | continue 464 | 465 | logging.info(f"Processing subfolder {subfolder}") 466 | 467 | all_remarks, file_remarks, should_display_hotness = \ 468 | optrecord.gather_results(filenames=files, num_jobs=args.jobs, 469 | exclude_names=args.exclude_names, 470 | exclude_text=args.exclude_text, 471 | collect_opt_success=args.collect_opt_success, 472 | annotate_external=args.annotate_external) 473 | 474 | map_remarks(all_remarks) 475 | 476 | generate_report(all_remarks=all_remarks, 477 | file_remarks=file_remarks, 478 | source_dir=source_dir, 479 | output_dir=os.path.join(args.output_dir, subfolder), 480 | should_display_hotness=should_display_hotness, 481 | num_jobs=args.jobs, 482 | open_browser=args.open_browser) 483 | else: # not split_top_foders 484 | files = optrecord.find_opt_files(os.path.join(*args.yaml_dirs_or_files)) 485 | if not files: 486 | parser.error("No *.opt.yaml files found") 487 | sys.exit(1) 488 | 489 | all_remarks, file_remarks, should_display_hotness = \ 490 | optrecord.gather_results(filenames=files, num_jobs=args.jobs, 491 | exclude_names=args.exclude_names, 492 | exclude_text=args.exclude_text, 493 | collect_opt_success=args.collect_opt_success, 494 | annotate_external=args.annotate_external) 495 | 496 | map_remarks(all_remarks) 497 | 498 | generate_report(all_remarks=all_remarks, 499 | file_remarks=file_remarks, 500 | source_dir=source_dir, 501 | output_dir=args.output_dir, 502 | should_display_hotness=should_display_hotness, 503 | num_jobs=args.jobs, 504 | open_browser=args.open_browser) 505 | 506 | end_time = datetime.now() 507 | logging.info(f"Ran for {end_time-start_time}") 508 | 509 | if __name__ == '__main__': 510 | main() 511 | 512 | -------------------------------------------------------------------------------- /optpmap.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import multiprocessing 3 | 4 | 5 | _current = None 6 | _total = None 7 | 8 | 9 | def _init(current, total): 10 | global _current 11 | global _total 12 | _current = current 13 | _total = total 14 | 15 | 16 | def _wrapped_func(func_and_args): 17 | func = func_and_args[0] 18 | args = func_and_args[1:] 19 | 20 | with _current.get_lock(): 21 | _current.value += 1 22 | sys.stdout.write('\r\t{} of {}'.format(_current.value, _total.value)) 23 | sys.stdout.flush() 24 | 25 | return func(*args) 26 | 27 | 28 | def pmap(func, iterable, processes, *args, **kwargs): 29 | """ 30 | A parallel map function that reports on its progress. 31 | 32 | Applies `func` to every item of `iterable` and return a list of the 33 | results. If `processes` is greater than one, a process pool is used to run 34 | the functions in parallel. 35 | """ 36 | global _current 37 | global _total 38 | _current = multiprocessing.Value('i', 0) 39 | _total = multiprocessing.Value('i', len(iterable)) 40 | 41 | func_and_args = [(func, it_arg, *args) for it_arg in iterable] 42 | if processes == 1: 43 | result = list(map(_wrapped_func, func_and_args)) 44 | else: 45 | pool = multiprocessing.Pool(initializer=_init, 46 | initargs=(_current, _total,), 47 | processes=processes, 48 | maxtasksperchild=2) 49 | result = pool.map(_wrapped_func, func_and_args) 50 | pool.close() 51 | pool.join() 52 | 53 | sys.stdout.write('\n') 54 | return result 55 | -------------------------------------------------------------------------------- /optrecord.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import logging 4 | logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") 5 | 6 | import io 7 | import yaml 8 | import pickle 9 | import platform 10 | import optrecord 11 | 12 | try: 13 | # Try to use the C parser. 14 | from yaml import CLoader as Loader 15 | except ImportError: 16 | logging.warning("For faster parsing, you may want to install libYAML for PyYAML") 17 | from yaml import Loader 18 | 19 | import html 20 | from collections import defaultdict 21 | import fnmatch 22 | import functools 23 | from multiprocessing import Lock 24 | import os, os.path 25 | import subprocess 26 | try: 27 | # The previously builtin function `intern()` was moved 28 | # to the `sys` module in Python 3. 29 | from sys import intern 30 | except: 31 | pass 32 | 33 | import re 34 | 35 | import optpmap 36 | 37 | try: 38 | dict.iteritems 39 | except AttributeError: 40 | # Python 3 41 | def itervalues(d): 42 | return iter(d.values()) 43 | def iteritems(d): 44 | return iter(d.items()) 45 | else: 46 | # Python 2 47 | def itervalues(d): 48 | return d.itervalues() 49 | def iteritems(d): 50 | return d.iteritems() 51 | 52 | 53 | def html_file_name(filename): 54 | replace_targets = ['/', '#', ':', '\\'] 55 | new_name = filename 56 | for target in replace_targets: 57 | new_name = new_name.replace(target, '_') 58 | return new_name + ".html" 59 | 60 | 61 | def make_link(File, Line): 62 | return "\"{}#L{}\"".format(html_file_name(File), Line) 63 | 64 | class EmptyLock(object): 65 | def __enter__(self): 66 | return True 67 | def __exit__(self, *args): 68 | pass 69 | 70 | class Remark(yaml.YAMLObject): 71 | # Work-around for http://pyyaml.org/ticket/154. 72 | yaml_loader = Loader 73 | 74 | default_demangler = 'c++filt -n -p' 75 | demangler_proc = None 76 | 77 | # @classmethod 78 | # def find_demangler(cls): 79 | 80 | 81 | @classmethod 82 | def open_demangler_proc(cls, demangler): 83 | cls.demangler_proc = subprocess.Popen(demangler.split(), stdin=subprocess.PIPE, stdout=subprocess.PIPE) 84 | 85 | @classmethod 86 | def set_demangler(cls, demangler): 87 | cls.open_demangler_proc(demangler) 88 | if (platform.system() == 'Windows'): 89 | cls.demangler_lock = EmptyLock(); # on windows we spawn demangler for each process, no Lock is needed 90 | else: 91 | cls.demangler_lock = Lock() 92 | 93 | 94 | @classmethod 95 | def demangle(cls, name): 96 | if not cls.demangler_proc: 97 | cls.set_demangler(cls.default_demangler) 98 | with cls.demangler_lock: 99 | cls.demangler_proc.stdin.write((name + '\n').encode('utf-8')) 100 | cls.demangler_proc.stdin.flush() 101 | return cls.demangler_proc.stdout.readline().rstrip().decode('utf-8') 102 | 103 | # Intern all strings since we have lot of duplication across filenames, 104 | # remark text. 105 | # 106 | # Change Args from a list of dicts to a tuple of tuples. This saves 107 | # memory in two ways. One, a small tuple is significantly smaller than a 108 | # small dict. Two, using tuple instead of list allows Args to be directly 109 | # used as part of the key (in Python only immutable types are hashable). 110 | def _reduce_memory(self): 111 | self.Pass = intern(self.Pass) 112 | self.Name = intern(self.Name) 113 | try: 114 | # Can't intern unicode strings. 115 | self.Function = intern(self.Function) 116 | except: 117 | pass 118 | 119 | def _reduce_memory_dict(old_dict): 120 | new_dict = dict() 121 | for (k, v) in iteritems(old_dict): 122 | if type(k) is str: 123 | k = intern(k) 124 | 125 | if type(v) is str: 126 | v = intern(v) 127 | elif type(v) is dict: 128 | # This handles [{'Caller': ..., 'DebugLoc': { 'File': ... }}] 129 | v = _reduce_memory_dict(v) 130 | new_dict[k] = v 131 | return tuple(new_dict.items()) 132 | 133 | self.Args = tuple([_reduce_memory_dict(arg_dict) for arg_dict in self.Args]) 134 | 135 | # The inverse operation of the dictonary-related memory optimization in 136 | # _reduce_memory_dict. E.g. 137 | # (('DebugLoc', (('File', ...) ... ))) -> [{'DebugLoc': {'File': ...} ....}] 138 | def recover_yaml_structure(self): 139 | def tuple_to_dict(t): 140 | d = dict() 141 | for (k, v) in t: 142 | if type(v) is tuple: 143 | v = tuple_to_dict(v) 144 | d[k] = v 145 | return d 146 | 147 | self.Args = [tuple_to_dict(arg_tuple) for arg_tuple in self.Args] 148 | 149 | def canonicalize(self): 150 | if not hasattr(self, 'Hotness'): 151 | self.Hotness = 0 152 | if not hasattr(self, 'Args'): 153 | self.Args = [] 154 | self._reduce_memory() 155 | 156 | @property 157 | def File(self): 158 | return self.DebugLoc['File'] 159 | 160 | @property 161 | def Line(self): 162 | return int(self.DebugLoc['Line']) 163 | 164 | @property 165 | def Column(self): 166 | return self.DebugLoc['Column'] 167 | 168 | @property 169 | def DebugLocString(self): 170 | return "{}:{}:{}".format(self.File, self.Line, self.Column) 171 | 172 | @property 173 | def DemangledFunctionName(self): 174 | return self.demangle(self.Function) 175 | 176 | @property 177 | def Link(self): 178 | return make_link(self.File, self.Line) 179 | 180 | def getArgString(self, mapping): 181 | mapping = dict(list(mapping)) 182 | dl = mapping.get('DebugLoc') 183 | if dl: 184 | del mapping['DebugLoc'] 185 | 186 | assert(len(mapping) == 1) 187 | (key, value) = list(mapping.items())[0] 188 | 189 | if key == 'Caller' or key == 'Callee' or key == 'DirectCallee': 190 | value = html.escape(self.demangle(value)) 191 | 192 | if dl and key != 'Caller': 193 | dl_dict = dict(list(dl)) 194 | return u"{}".format( 195 | make_link(dl_dict['File'], dl_dict['Line']), value) 196 | else: 197 | return value 198 | 199 | # Return a cached dictionary for the arguments. The key for each entry is 200 | # the argument key (e.g. 'Callee' for inlining remarks. The value is a 201 | # list containing the value (e.g. for 'Callee' the function) and 202 | # optionally a DebugLoc. 203 | def getArgDict(self): 204 | if hasattr(self, 'ArgDict'): 205 | return self.ArgDict 206 | self.ArgDict = {} 207 | for arg in self.Args: 208 | if len(arg) == 2: 209 | if arg[0][0] == 'DebugLoc': 210 | dbgidx = 0 211 | else: 212 | assert(arg[1][0] == 'DebugLoc') 213 | dbgidx = 1 214 | 215 | key = arg[1 - dbgidx][0] 216 | entry = (arg[1 - dbgidx][1], arg[dbgidx][1]) 217 | else: 218 | arg = arg[0] 219 | key = arg[0] 220 | entry = (arg[1], ) 221 | 222 | self.ArgDict[key] = entry 223 | return self.ArgDict 224 | 225 | def getDiffPrefix(self): 226 | if hasattr(self, 'Added'): 227 | if self.Added: 228 | return '+' 229 | else: 230 | return '-' 231 | return '' 232 | 233 | @property 234 | def PassWithDiffPrefix(self): 235 | return self.getDiffPrefix() + self.Pass 236 | 237 | @property 238 | def message(self): 239 | # Args is a list of mappings (dictionaries) 240 | values = [self.getArgString(mapping) for mapping in self.Args] 241 | return "".join(values) 242 | 243 | @property 244 | def RelativeHotness(self): 245 | if self.max_hotness: 246 | return "{0:.2f}%".format(self.Hotness * 100. / self.max_hotness) 247 | else: 248 | return '' 249 | 250 | @property 251 | def key(self): 252 | return (self.__class__, self.PassWithDiffPrefix, self.Name, self.File, 253 | self.Line, self.Column, self.Function, self.Args) 254 | 255 | def __hash__(self): 256 | return hash(self.key) 257 | 258 | def __eq__(self, other): 259 | return self.key == other.key 260 | 261 | def __repr__(self): 262 | return str(self.key) 263 | 264 | 265 | class Analysis(Remark): 266 | yaml_tag = '!Analysis' 267 | 268 | @property 269 | def color(self): 270 | return "white" 271 | 272 | 273 | class AnalysisFPCommute(Analysis): 274 | yaml_tag = '!AnalysisFPCommute' 275 | 276 | 277 | class AnalysisAliasing(Analysis): 278 | yaml_tag = '!AnalysisAliasing' 279 | 280 | 281 | class Passed(Remark): 282 | yaml_tag = '!Passed' 283 | 284 | @property 285 | def color(self): 286 | return "green" 287 | 288 | 289 | class Missed(Remark): 290 | yaml_tag = '!Missed' 291 | 292 | @property 293 | def color(self): 294 | return "red" 295 | 296 | class Failure(Missed): 297 | yaml_tag = '!Failure' 298 | 299 | def get_remarks(input_file, exclude_names=None, exclude_text = None, collect_opt_success=False, annotate_external=False): 300 | max_hotness = 0 301 | all_remarks = dict() 302 | file_remarks = defaultdict(functools.partial(defaultdict, list)) 303 | #logging.debug(f"Parsing {input_file}") 304 | 305 | #TODO: filter unique name+file+line loc *here* 306 | with io.open(input_file, encoding = 'utf-8') as f: 307 | docs = yaml.load_all(f, Loader=Loader) 308 | 309 | exclude_text_e = None 310 | if exclude_text: 311 | exclude_text_e = re.compile(exclude_text) 312 | exclude_names_e = None 313 | if exclude_names: 314 | exclude_names_e = re.compile(exclude_names) 315 | for remark in docs: 316 | remark.canonicalize() 317 | # Avoid remarks withoug debug location or if they are duplicated 318 | if not hasattr(remark, 'DebugLoc') or remark.key in all_remarks: 319 | continue 320 | 321 | if not collect_opt_success and not (isinstance(remark, optrecord.Missed) | isinstance(remark, optrecord.Failure)): 322 | continue 323 | 324 | if not annotate_external: 325 | if os.path.isabs(remark.File): 326 | continue 327 | 328 | if exclude_names_e and exclude_names_e.search(remark.Name): 329 | continue 330 | 331 | if exclude_text_e and exclude_text_e.search(remark.message): 332 | continue 333 | 334 | all_remarks[remark.key] = remark 335 | 336 | file_remarks[remark.File][remark.Line].append(remark) 337 | 338 | # If we're reading a back a diff yaml file, max_hotness is already 339 | # captured which may actually be less than the max hotness found 340 | # in the file. 341 | if hasattr(remark, 'max_hotness'): 342 | max_hotness = remark.max_hotness 343 | max_hotness = max(max_hotness, remark.Hotness) 344 | 345 | return max_hotness, all_remarks, file_remarks 346 | 347 | 348 | def gather_results(filenames, num_jobs, annotate_external=False, exclude_names=None, exclude_text=None, collect_opt_success=False): 349 | logging.info('Reading YAML files...') 350 | 351 | remarks = optpmap.pmap( 352 | get_remarks, filenames, num_jobs, exclude_names, exclude_text, collect_opt_success, annotate_external) 353 | 354 | max_hotness = max(entry[0] for entry in remarks) 355 | 356 | def merge_file_remarks(file_remarks_job, all_remarks, merged): 357 | for filename, d in iteritems(file_remarks_job): 358 | for line, remarks in iteritems(d): 359 | for remark in remarks: 360 | # Bring max_hotness into the remarks so that 361 | # RelativeHotness does not depend on an external global. 362 | remark.max_hotness = max_hotness 363 | if remark.key not in all_remarks: 364 | merged[filename][line].append(remark) 365 | 366 | all_remarks = dict() 367 | file_remarks = defaultdict(functools.partial(defaultdict, list)) 368 | for _, all_remarks_job, file_remarks_job in remarks: 369 | merge_file_remarks(file_remarks_job, all_remarks, file_remarks) 370 | all_remarks.update(all_remarks_job) 371 | 372 | return all_remarks, file_remarks, max_hotness != 0 373 | 374 | 375 | def find_opt_files(*dirs_or_files): 376 | all = [] 377 | for dir_or_file in dirs_or_files: 378 | if os.path.isfile(dir_or_file): 379 | all.append(dir_or_file) 380 | else: 381 | for dir, subdirs, files in os.walk(dir_or_file): 382 | # Exclude mounted directories and symlinks (os.walk default). 383 | subdirs[:] = [d for d in subdirs 384 | if not os.path.ismount(os.path.join(dir, d))] 385 | for file in files: 386 | if fnmatch.fnmatch(file, "*.opt.yaml*"): 387 | all.append(os.path.join(dir, file)) 388 | return all 389 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PyYAML>=3.12 2 | Pygments>=2.2.0 3 | #guppy>=0.1.11 4 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | .source { 2 | table-layout: fixed; 3 | width: 100%; 4 | white-space: nowrap; 5 | } 6 | .source td { 7 | white-space: nowrap; 8 | overflow: hidden; 9 | text-overflow: ellipsis; 10 | } 11 | .red { 12 | background-color: #ffd0d0; 13 | } 14 | .cyan { 15 | background-color: cyan; 16 | } 17 | body { 18 | font-family: -apple-system, sans-serif; 19 | } 20 | pre { 21 | margin-top: 0px !important; 22 | margin-bottom: 0px !important; 23 | } 24 | .source-name-title { 25 | padding: 5px 10px; 26 | border-bottom: 1px solid #dbdbdb; 27 | background-color: #eee; 28 | line-height: 35px; 29 | } 30 | .centered { 31 | display: table; 32 | margin-left: left; 33 | margin-right: auto; 34 | border: 1px solid #dbdbdb; 35 | border-radius: 3px; 36 | } 37 | .expansion-view { 38 | background-color: rgba(0, 0, 0, 0); 39 | margin-left: 0px; 40 | margin-top: 5px; 41 | margin-right: 5px; 42 | margin-bottom: 5px; 43 | border: 1px solid #dbdbdb; 44 | border-radius: 3px; 45 | } 46 | table { 47 | border-collapse: collapse; 48 | } 49 | .light-row { 50 | background: #ffffff; 51 | border: 1px solid #dbdbdb; 52 | } 53 | .column-entry { 54 | text-align: right; 55 | } 56 | .column-entry-left { 57 | text-align: left; 58 | } 59 | .column-entry-white { 60 | text-align: right; 61 | background-color: #ffffff; 62 | } 63 | .column-entry-red { 64 | text-align: right; 65 | background-color: #ffd0d0; 66 | } 67 | .column-entry-green { 68 | text-align: right; 69 | background-color: #d0ffd0; 70 | } 71 | .column-entry-yellow { 72 | text-align: left; 73 | background-color: #ffe1a6; 74 | } 75 | .column-entry-0 { 76 | background-color: #ffffff; 77 | } 78 | .column-entry-1 { 79 | background-color: #eeeeee; 80 | } 81 | .line-number { 82 | text-align: right; 83 | color: #aaa; 84 | } 85 | .covered-line { 86 | text-align: right; 87 | color: #0080ff; 88 | } 89 | .uncovered-line { 90 | text-align: right; 91 | color: #ff3300; 92 | } 93 | .tooltip { 94 | position: relative; 95 | display: inline; 96 | background-color: #b3e6ff; 97 | text-decoration: none; 98 | } 99 | .tooltip span.tooltip-content { 100 | position: absolute; 101 | width: 100px; 102 | margin-left: -50px; 103 | color: #FFFFFF; 104 | background: #000000; 105 | height: 30px; 106 | line-height: 30px; 107 | text-align: center; 108 | visibility: hidden; 109 | border-radius: 6px; 110 | } 111 | .tooltip span.tooltip-content:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | left: 50%; 116 | margin-left: -8px; 117 | width: 0; height: 0; 118 | border-top: 8px solid #000000; 119 | border-right: 8px solid transparent; 120 | border-left: 8px solid transparent; 121 | } 122 | :hover.tooltip span.tooltip-content { 123 | visibility: visible; 124 | opacity: 0.8; 125 | bottom: 30px; 126 | left: 50%; 127 | z-index: 999; 128 | } 129 | th, td { 130 | vertical-align: top; 131 | padding: 2px 5px; 132 | border-collapse: collapse; 133 | border-right: solid 1px #eee; 134 | border-left: solid 1px #eee; 135 | } 136 | td:first-child { 137 | border-left: none; 138 | } 139 | td:last-child { 140 | border-right: none; 141 | } 142 | .expanded { 143 | background-color: #f2f2f2; 144 | padding-top: 5px; 145 | padding-left: 5px; 146 | } 147 | .col-left { 148 | float: left; 149 | margin-bottom: -99999px; 150 | padding-bottom: 99999px; 151 | } 152 | 153 | /* Generated with pygmentize -S colorful -f html >> style.css */ 154 | 155 | .hll { background-color: #ffffcc } 156 | .c { color: #888888 } /* Comment */ 157 | .err { color: #FF0000; background-color: #FFAAAA } /* Error */ 158 | .k { color: #008800; font-weight: bold } /* Keyword */ 159 | .o { color: #333333 } /* Operator */ 160 | .ch { color: #888888 } /* Comment.Hashbang */ 161 | .cm { color: #888888 } /* Comment.Multiline */ 162 | .cp { color: #557799 } /* Comment.Preproc */ 163 | .cpf { color: #888888 } /* Comment.PreprocFile */ 164 | .c1 { color: #888888 } /* Comment.Single */ 165 | .cs { color: #cc0000; font-weight: bold } /* Comment.Special */ 166 | .gd { color: #A00000 } /* Generic.Deleted */ 167 | .ge { font-style: italic } /* Generic.Emph */ 168 | .gr { color: #FF0000 } /* Generic.Error */ 169 | .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 170 | .gi { color: #00A000 } /* Generic.Inserted */ 171 | .go { color: #888888 } /* Generic.Output */ 172 | .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 173 | .gs { font-weight: bold } /* Generic.Strong */ 174 | .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 175 | .gt { color: #0044DD } /* Generic.Traceback */ 176 | .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ 177 | .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ 178 | .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ 179 | .kp { color: #003388; font-weight: bold } /* Keyword.Pseudo */ 180 | .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ 181 | .kt { color: #333399; font-weight: bold } /* Keyword.Type */ 182 | .m { color: #6600EE; font-weight: bold } /* Literal.Number */ 183 | .s { background-color: #fff0f0 } /* Literal.String */ 184 | .na { color: #0000CC } /* Name.Attribute */ 185 | .nb { color: #007020 } /* Name.Builtin */ 186 | .nc { color: #BB0066; font-weight: bold } /* Name.Class */ 187 | .no { color: #003366; font-weight: bold } /* Name.Constant */ 188 | .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 189 | .ni { color: #880000; font-weight: bold } /* Name.Entity */ 190 | .ne { color: #FF0000; font-weight: bold } /* Name.Exception */ 191 | .nf { color: #0066BB; font-weight: bold } /* Name.Function */ 192 | .nl { color: #997700; font-weight: bold } /* Name.Label */ 193 | .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 194 | .nt { color: #007700 } /* Name.Tag */ 195 | .nv { color: #996633 } /* Name.Variable */ 196 | .ow { color: #000000; font-weight: bold } /* Operator.Word */ 197 | .w { color: #bbbbbb } /* Text.Whitespace */ 198 | .mb { color: #6600EE; font-weight: bold } /* Literal.Number.Bin */ 199 | .mf { color: #6600EE; font-weight: bold } /* Literal.Number.Float */ 200 | .mh { color: #005588; font-weight: bold } /* Literal.Number.Hex */ 201 | .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ 202 | .mo { color: #4400EE; font-weight: bold } /* Literal.Number.Oct */ 203 | .sb { background-color: #fff0f0 } /* Literal.String.Backtick */ 204 | .sc { color: #0044DD } /* Literal.String.Char */ 205 | .sd { color: #DD4422 } /* Literal.String.Doc */ 206 | .s2 { background-color: #fff0f0 } /* Literal.String.Double */ 207 | .se { color: #666666; font-weight: bold; background-color: #fff0f0 } /* Literal.String.Escape */ 208 | .sh { background-color: #fff0f0 } /* Literal.String.Heredoc */ 209 | .si { background-color: #eeeeee } /* Literal.String.Interpol */ 210 | .sx { color: #DD2200; background-color: #fff0f0 } /* Literal.String.Other */ 211 | .sr { color: #000000; background-color: #fff0ff } /* Literal.String.Regex */ 212 | .s1 { background-color: #fff0f0 } /* Literal.String.Single */ 213 | .ss { color: #AA6600 } /* Literal.String.Symbol */ 214 | .bp { color: #007020 } /* Name.Builtin.Pseudo */ 215 | .vc { color: #336699 } /* Name.Variable.Class */ 216 | .vg { color: #dd7700; font-weight: bold } /* Name.Variable.Global */ 217 | .vi { color: #3333BB } /* Name.Variable.Instance */ 218 | .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */ 219 | --------------------------------------------------------------------------------