From 3529d05ccc7c13c9ff5c4e0ba5264eda7e3ea8c8 Mon Sep 17 00:00:00 2001 From: Jessica Leveque <leveque@lapp.in2p3.fr> Date: Wed, 30 Jan 2013 15:43:38 +0100 Subject: [PATCH] tagging CoolRunQuery-00-04-50 (CoolRunQuery-00-04-50) --- Database/CoolRunQuery/cmt/requirements | 35 + Database/CoolRunQuery/doc/README | 5 + Database/CoolRunQuery/doc/todo.txt | 30 + .../CoolRunQuery/html/LumiRangeCollection.dtd | 25 + .../CoolRunQuery/html/atlas-runquery-help.css | 54 + .../html/atlas-runquery-help.html | 84 + .../CoolRunQuery/html/atlas-runquery-lb.css | 56 + .../html/atlas-runquery-maintanancenote.html | 80 + .../html/atlas-runquery-print.css | 33 + .../html/atlas-runquery-relnotes.html | 121 + .../html/atlas-runquery-results.css | 158 ++ .../html/atlas-runquery-tabview.css | 38 + Database/CoolRunQuery/html/atlas-runquery.css | 411 +++ .../CoolRunQuery/html/atlas-runquery.html | 252 ++ Database/CoolRunQuery/html/atlas1.ico | Bin 0 -> 894 bytes .../CoolRunQuery/html/images/1332500.thm.gif | Bin 0 -> 398 bytes .../CoolRunQuery/html/images/arrow-down.gif | Bin 0 -> 852 bytes .../CoolRunQuery/html/images/arrow-up.gif | Bin 0 -> 851 bytes Database/CoolRunQuery/html/images/atlas1.ico | Bin 0 -> 894 bytes Database/CoolRunQuery/html/images/book.gif | Bin 0 -> 361 bytes Database/CoolRunQuery/html/images/cclose.jpg | Bin 0 -> 741 bytes Database/CoolRunQuery/html/images/close.gif | Bin 0 -> 381 bytes Database/CoolRunQuery/html/images/copen.jpg | Bin 0 -> 749 bytes Database/CoolRunQuery/html/images/days.gif | Bin 0 -> 161 bytes .../CoolRunQuery/html/images/download.gif | Bin 0 -> 120 bytes Database/CoolRunQuery/html/images/i_icon.png | Bin 0 -> 4029 bytes Database/CoolRunQuery/html/images/info.gif | Bin 0 -> 212 bytes Database/CoolRunQuery/html/images/minus.gif | Bin 0 -> 974 bytes .../CoolRunQuery/html/images/newtopic.gif | Bin 0 -> 340 bytes Database/CoolRunQuery/html/images/open.gif | Bin 0 -> 389 bytes Database/CoolRunQuery/html/images/plus.gif | Bin 0 -> 981 bytes Database/CoolRunQuery/html/images/printer.png | Bin 0 -> 13761 bytes .../CoolRunQuery/html/images/printer2.png | Bin 0 -> 38049 bytes .../CoolRunQuery/html/images/printtopic.gif | Bin 0 -> 221 bytes .../CoolRunQuery/html/images/question-1.gif | Bin 0 -> 214 bytes .../CoolRunQuery/html/images/question.gif | Bin 0 -> 214 bytes .../html/images/recentchanges.gif | Bin 0 -> 220 bytes .../html/images/rootdrawing-logo.png | Bin 0 -> 15051 bytes .../CoolRunQuery/html/images/rootlogo.gif | Bin 0 -> 19590 bytes .../CoolRunQuery/html/images/statistics.gif | Bin 0 -> 118 bytes .../CoolRunQuery/html/images/tiny_red.gif | Bin 0 -> 310 bytes .../CoolRunQuery/html/images/tree_icon.gif | Bin 0 -> 1891 bytes .../CoolRunQuery/html/images/viewtopic.gif | Bin 0 -> 77 bytes Database/CoolRunQuery/html/images/wip.gif | Bin 0 -> 594 bytes .../CoolRunQuery/html/images/xml-feed.gif | Bin 0 -> 429 bytes .../CoolRunQuery/html/images/xml-small.gif | Bin 0 -> 121 bytes .../CoolRunQuery/html/js/animatedcollapse.js | 223 ++ Database/CoolRunQuery/html/js/dw_event.js | 41 + Database/CoolRunQuery/html/js/dw_tooltip.js | 502 ++++ .../CoolRunQuery/html/js/dw_tooltip_aux.js | 178 ++ Database/CoolRunQuery/html/js/dw_viewport.js | 61 + Database/CoolRunQuery/html/js/jquery.min.js | 19 + .../html/js/jquery.popupWindow.js | 62 + Database/CoolRunQuery/html/js/rq.js | 48 + Database/CoolRunQuery/html/js/rq_examples.js | 500 ++++ .../CoolRunQuery/html/js/rq_simplepopups.js | 75 + Database/CoolRunQuery/html/js/switcher.js | 97 + Database/CoolRunQuery/html/js/tabView.js | 84 + .../CoolRunQuery/macros/AtlRunQueryGraphs.C | 4 + Database/CoolRunQuery/macros/colours.C | 68 + Database/CoolRunQuery/macros/style.C | 39 + .../CoolRunQuery/python/AtlRunQueryAMI.py | 231 ++ .../CoolRunQuery/python/AtlRunQueryCOMA.py | 178 ++ .../CoolRunQuery/python/AtlRunQueryLib.py | 349 +++ .../CoolRunQuery/python/AtlRunQueryLookup.py | 3 + .../CoolRunQuery/python/AtlRunQueryOptions.py | 237 ++ .../CoolRunQuery/python/AtlRunQueryPVSS.py | 34 + .../CoolRunQuery/python/AtlRunQueryParser.py | 839 +++++++ .../python/AtlRunQueryQueryConfig.py | 24 + .../CoolRunQuery/python/AtlRunQueryRun.py | 2209 +++++++++++++++++ .../CoolRunQuery/python/AtlRunQuerySFO.py | 438 ++++ .../python/AtlRunQuerySelectorWorker.py | 303 +++ .../CoolRunQuery/python/AtlRunQueryTier0.py | 54 + .../CoolRunQuery/python/AtlRunQueryVersion.py | 3 + Database/CoolRunQuery/python/__init__.py | 7 + .../python/html/AtlRunQueryDQSummary.py | 969 ++++++++ .../python/html/AtlRunQueryHTML.py | 377 +++ .../python/html/AtlRunQueryHtmlUtils.py | 435 ++++ .../python/html/AtlRunQueryPageMaker.py | 50 + .../python/html/AtlRunQuerySummary.py | 651 +++++ Database/CoolRunQuery/python/html/__init__.py | 2 + .../python/output/AtlRunQueryRoot.py | 975 ++++++++ .../python/output/AtlRunQuerySave.py | 152 ++ .../python/output/AtlRunQueryXML.py | 277 +++ .../CoolRunQuery/python/output/__init__.py | 2 + .../selector/AtlRunQuerySelectorBase.py | 609 +++++ .../python/selector/AtlRunQuerySelectorDQ.py | 697 ++++++ .../selector/AtlRunQuerySelectorEvents.py | 132 + .../selector/AtlRunQuerySelectorLhcOlc.py | 488 ++++ .../selector/AtlRunQuerySelectorMisc.py | 335 +++ .../selector/AtlRunQuerySelectorRuntime.py | 133 + .../selector/AtlRunQuerySelectorStreams.py | 303 +++ .../selector/AtlRunQuerySelectorTrigger.py | 588 +++++ .../CoolRunQuery/python/selector/__init__.py | 2 + .../python/utils/AtlRunQueryCache.py | 89 + .../python/utils/AtlRunQueryDQUtils.py | 221 ++ .../python/utils/AtlRunQueryFastBlobRead.py | 21 + .../python/utils/AtlRunQueryIOV.py | 118 + .../python/utils/AtlRunQueryLookup.py | 290 +++ .../python/utils/AtlRunQueryMemUtil.py | 45 + .../python/utils/AtlRunQueryTimer.py | 104 + .../python/utils/AtlRunQueryTriggerUtils.py | 285 +++ .../python/utils/AtlRunQueryUtils.py | 633 +++++ .../CoolRunQuery/python/utils/__init__.py | 2 + Database/CoolRunQuery/share/AtlRunQuery.py | 83 + .../CoolRunQuery/share/AtlRunQueryGraphs.py | 116 + .../CoolRunQuery/share/CoolRunQueryWrapper.sh | 47 + .../share/CoolRunQueryWrapper47.sh | 37 + Database/CoolRunQuery/share/easycommit | 124 + Database/CoolRunQuery/share/grepfile.py | 95 + Database/CoolRunQuery/share/query.py | 114 + Database/CoolRunQuery/share/subproc.py | 37 + 112 files changed, 17930 insertions(+) create mode 100644 Database/CoolRunQuery/cmt/requirements create mode 100644 Database/CoolRunQuery/doc/README create mode 100644 Database/CoolRunQuery/doc/todo.txt create mode 100644 Database/CoolRunQuery/html/LumiRangeCollection.dtd create mode 100644 Database/CoolRunQuery/html/atlas-runquery-help.css create mode 100644 Database/CoolRunQuery/html/atlas-runquery-help.html create mode 100644 Database/CoolRunQuery/html/atlas-runquery-lb.css create mode 100644 Database/CoolRunQuery/html/atlas-runquery-maintanancenote.html create mode 100644 Database/CoolRunQuery/html/atlas-runquery-print.css create mode 100644 Database/CoolRunQuery/html/atlas-runquery-relnotes.html create mode 100644 Database/CoolRunQuery/html/atlas-runquery-results.css create mode 100644 Database/CoolRunQuery/html/atlas-runquery-tabview.css create mode 100644 Database/CoolRunQuery/html/atlas-runquery.css create mode 100644 Database/CoolRunQuery/html/atlas-runquery.html create mode 100644 Database/CoolRunQuery/html/atlas1.ico create mode 100644 Database/CoolRunQuery/html/images/1332500.thm.gif create mode 100644 Database/CoolRunQuery/html/images/arrow-down.gif create mode 100644 Database/CoolRunQuery/html/images/arrow-up.gif create mode 100644 Database/CoolRunQuery/html/images/atlas1.ico create mode 100644 Database/CoolRunQuery/html/images/book.gif create mode 100644 Database/CoolRunQuery/html/images/cclose.jpg create mode 100644 Database/CoolRunQuery/html/images/close.gif create mode 100644 Database/CoolRunQuery/html/images/copen.jpg create mode 100644 Database/CoolRunQuery/html/images/days.gif create mode 100644 Database/CoolRunQuery/html/images/download.gif create mode 100644 Database/CoolRunQuery/html/images/i_icon.png create mode 100644 Database/CoolRunQuery/html/images/info.gif create mode 100644 Database/CoolRunQuery/html/images/minus.gif create mode 100644 Database/CoolRunQuery/html/images/newtopic.gif create mode 100644 Database/CoolRunQuery/html/images/open.gif create mode 100644 Database/CoolRunQuery/html/images/plus.gif create mode 100644 Database/CoolRunQuery/html/images/printer.png create mode 100644 Database/CoolRunQuery/html/images/printer2.png create mode 100644 Database/CoolRunQuery/html/images/printtopic.gif create mode 100644 Database/CoolRunQuery/html/images/question-1.gif create mode 100644 Database/CoolRunQuery/html/images/question.gif create mode 100644 Database/CoolRunQuery/html/images/recentchanges.gif create mode 100644 Database/CoolRunQuery/html/images/rootdrawing-logo.png create mode 100644 Database/CoolRunQuery/html/images/rootlogo.gif create mode 100644 Database/CoolRunQuery/html/images/statistics.gif create mode 100644 Database/CoolRunQuery/html/images/tiny_red.gif create mode 100644 Database/CoolRunQuery/html/images/tree_icon.gif create mode 100644 Database/CoolRunQuery/html/images/viewtopic.gif create mode 100644 Database/CoolRunQuery/html/images/wip.gif create mode 100644 Database/CoolRunQuery/html/images/xml-feed.gif create mode 100644 Database/CoolRunQuery/html/images/xml-small.gif create mode 100644 Database/CoolRunQuery/html/js/animatedcollapse.js create mode 100755 Database/CoolRunQuery/html/js/dw_event.js create mode 100755 Database/CoolRunQuery/html/js/dw_tooltip.js create mode 100755 Database/CoolRunQuery/html/js/dw_tooltip_aux.js create mode 100755 Database/CoolRunQuery/html/js/dw_viewport.js create mode 100644 Database/CoolRunQuery/html/js/jquery.min.js create mode 100644 Database/CoolRunQuery/html/js/jquery.popupWindow.js create mode 100644 Database/CoolRunQuery/html/js/rq.js create mode 100644 Database/CoolRunQuery/html/js/rq_examples.js create mode 100644 Database/CoolRunQuery/html/js/rq_simplepopups.js create mode 100644 Database/CoolRunQuery/html/js/switcher.js create mode 100644 Database/CoolRunQuery/html/js/tabView.js create mode 100644 Database/CoolRunQuery/macros/AtlRunQueryGraphs.C create mode 100644 Database/CoolRunQuery/macros/colours.C create mode 100644 Database/CoolRunQuery/macros/style.C create mode 100644 Database/CoolRunQuery/python/AtlRunQueryAMI.py create mode 100755 Database/CoolRunQuery/python/AtlRunQueryCOMA.py create mode 100755 Database/CoolRunQuery/python/AtlRunQueryLib.py create mode 100644 Database/CoolRunQuery/python/AtlRunQueryLookup.py create mode 100644 Database/CoolRunQuery/python/AtlRunQueryOptions.py create mode 100644 Database/CoolRunQuery/python/AtlRunQueryPVSS.py create mode 100755 Database/CoolRunQuery/python/AtlRunQueryParser.py create mode 100644 Database/CoolRunQuery/python/AtlRunQueryQueryConfig.py create mode 100644 Database/CoolRunQuery/python/AtlRunQueryRun.py create mode 100644 Database/CoolRunQuery/python/AtlRunQuerySFO.py create mode 100644 Database/CoolRunQuery/python/AtlRunQuerySelectorWorker.py create mode 100644 Database/CoolRunQuery/python/AtlRunQueryTier0.py create mode 100644 Database/CoolRunQuery/python/AtlRunQueryVersion.py create mode 100644 Database/CoolRunQuery/python/__init__.py create mode 100755 Database/CoolRunQuery/python/html/AtlRunQueryDQSummary.py create mode 100644 Database/CoolRunQuery/python/html/AtlRunQueryHTML.py create mode 100755 Database/CoolRunQuery/python/html/AtlRunQueryHtmlUtils.py create mode 100644 Database/CoolRunQuery/python/html/AtlRunQueryPageMaker.py create mode 100644 Database/CoolRunQuery/python/html/AtlRunQuerySummary.py create mode 100644 Database/CoolRunQuery/python/html/__init__.py create mode 100644 Database/CoolRunQuery/python/output/AtlRunQueryRoot.py create mode 100644 Database/CoolRunQuery/python/output/AtlRunQuerySave.py create mode 100644 Database/CoolRunQuery/python/output/AtlRunQueryXML.py create mode 100644 Database/CoolRunQuery/python/output/__init__.py create mode 100644 Database/CoolRunQuery/python/selector/AtlRunQuerySelectorBase.py create mode 100644 Database/CoolRunQuery/python/selector/AtlRunQuerySelectorDQ.py create mode 100644 Database/CoolRunQuery/python/selector/AtlRunQuerySelectorEvents.py create mode 100644 Database/CoolRunQuery/python/selector/AtlRunQuerySelectorLhcOlc.py create mode 100644 Database/CoolRunQuery/python/selector/AtlRunQuerySelectorMisc.py create mode 100644 Database/CoolRunQuery/python/selector/AtlRunQuerySelectorRuntime.py create mode 100644 Database/CoolRunQuery/python/selector/AtlRunQuerySelectorStreams.py create mode 100644 Database/CoolRunQuery/python/selector/AtlRunQuerySelectorTrigger.py create mode 100644 Database/CoolRunQuery/python/selector/__init__.py create mode 100755 Database/CoolRunQuery/python/utils/AtlRunQueryCache.py create mode 100644 Database/CoolRunQuery/python/utils/AtlRunQueryDQUtils.py create mode 100644 Database/CoolRunQuery/python/utils/AtlRunQueryFastBlobRead.py create mode 100644 Database/CoolRunQuery/python/utils/AtlRunQueryIOV.py create mode 100644 Database/CoolRunQuery/python/utils/AtlRunQueryLookup.py create mode 100644 Database/CoolRunQuery/python/utils/AtlRunQueryMemUtil.py create mode 100644 Database/CoolRunQuery/python/utils/AtlRunQueryTimer.py create mode 100755 Database/CoolRunQuery/python/utils/AtlRunQueryTriggerUtils.py create mode 100644 Database/CoolRunQuery/python/utils/AtlRunQueryUtils.py create mode 100644 Database/CoolRunQuery/python/utils/__init__.py create mode 100755 Database/CoolRunQuery/share/AtlRunQuery.py create mode 100755 Database/CoolRunQuery/share/AtlRunQueryGraphs.py create mode 100755 Database/CoolRunQuery/share/CoolRunQueryWrapper.sh create mode 100755 Database/CoolRunQuery/share/CoolRunQueryWrapper47.sh create mode 100755 Database/CoolRunQuery/share/easycommit create mode 100644 Database/CoolRunQuery/share/grepfile.py create mode 100644 Database/CoolRunQuery/share/query.py create mode 100644 Database/CoolRunQuery/share/subproc.py diff --git a/Database/CoolRunQuery/cmt/requirements b/Database/CoolRunQuery/cmt/requirements new file mode 100644 index 00000000000..b7007522dc5 --- /dev/null +++ b/Database/CoolRunQuery/cmt/requirements @@ -0,0 +1,35 @@ +# $Id: requirements,v 1.2 2008-11-19 18:56:51 stelzer Exp $ + +package CoolRunQuery + +author Andreas.Hoecker@cern.ch Joerg.Stelzer@cern.ch +manager Andreas.Hoecker@cern.ch Joerg.Stelzer@cern.ch + +use AtlasPolicy * + + +pattern declare_arq_python_modules \ + apply_pattern generic_declare_for_link kind=python_modules_<dir> files='-s=../python <files>' prefix=python/<package>/<dir> name=arqpy<dir> ; \ + private ; \ + macro_append <package>_python_init_dependencies " install_arqpy<dir>python_modules " ; \ + end_private + + + +apply_pattern declare_arq_python_modules files="*.py" dir="" +apply_pattern declare_arq_python_modules files="html/*.py" dir="html" +apply_pattern declare_arq_python_modules files="selector/*.py" dir="selector" +apply_pattern declare_arq_python_modules files="utils/*.py" dir="utils" +apply_pattern declare_arq_python_modules files="output/*.py" dir="output" + +apply_pattern declare_scripts files="*.py *.C" + +pattern declare_xml \ + apply_pattern generic_declare_for_link kind=xml files='-s=../html <files>' prefix=XML/<package> name=<name> + +pattern declare_html \ + apply_pattern generic_declare_for_link kind=html files='-s=../html <files>' prefix=html/<package> name=<name> + +apply_pattern declare_xml files="*.html *.dtd" + +apply_pattern declare_html files="*.html *.dtd *.css" diff --git a/Database/CoolRunQuery/doc/README b/Database/CoolRunQuery/doc/README new file mode 100644 index 00000000000..1ff8cb8a114 --- /dev/null +++ b/Database/CoolRunQuery/doc/README @@ -0,0 +1,5 @@ +SVN updates + +1) svn up and svn ci +2) tagsvn.sh CoolRunQuery-00-00-14 (was immer du zum taggen benutzt) +3) auf voatlas24 in /var/www/html/atlas-runquery: . ./switchtag.sh hoecker 00-00-14 diff --git a/Database/CoolRunQuery/doc/todo.txt b/Database/CoolRunQuery/doc/todo.txt new file mode 100644 index 00000000000..2de2546d3f4 --- /dev/null +++ b/Database/CoolRunQuery/doc/todo.txt @@ -0,0 +1,30 @@ +Run Query - todo + +Features: + +- fix order of output +- examples sollten gleich bleiben nach ausfuehren des queries (very difficult) +- Show: add L1 trigger rates (eg, 'show trates') +- Show atlas runtype + +- add new COOL folders from Armin about Tier-0 reconstruction efficiency + /afs/cern.ch/atlas/project/tzero/prod1/projects/data09/rfc/RawFileCounter.sh +- nicify dataset info on CAF + +- Payload based query +- Query caching: have the web page saved in a cache if one wants to send the query results to someone (Andreas) +- Add path to CASTOR for datasets (wish from Sylvie) +- Click on column title of selected runs and sort table accordingly (number of events, LBs, duration, etc) + +- Create API for external access +- Print default settings to web page + +- Better error report, maybe popup window + +- Be able to query on number of events pasing a certain trigger chain or selecting on prescales/pass through of a trigger (or PSKs) -> see email from Kenneth Johns <kjohns@mail.cern.ch>. + +- Add luminosity information + +Bugs: + +- this probably does not work yet: showing the same DQ flag from two different folders diff --git a/Database/CoolRunQuery/html/LumiRangeCollection.dtd b/Database/CoolRunQuery/html/LumiRangeCollection.dtd new file mode 100644 index 00000000000..2d11b4575ac --- /dev/null +++ b/Database/CoolRunQuery/html/LumiRangeCollection.dtd @@ -0,0 +1,25 @@ +<!ELEMENT LumiRangeCollection (NamedLumiRange*)> + +<!ELEMENT NamedLumiRange (Name?, Version?, Metadata*, LumiBlockCollection*)> + +<!ELEMENT Metadata (#PCDATA|Stream)*> +<!ATTLIST Metadata Name CDATA #REQUIRED> + +<!ELEMENT Stream EMPTY> +<!ATTLIST Stream + Name CDATA #REQUIRED + TotalNumOfEvents CDATA #IMPLIED + NumOfSelectedEvents CDATA #IMPLIED +> + +<!ELEMENT LumiBlockCollection (Run, LBRange*)> + +<!ELEMENT Name (#PCDATA)> +<!ELEMENT Version (#PCDATA)> +<!ELEMENT Run (#PCDATA)> +<!ATTLIST Run PrescaleRD0 CDATA #IMPLIED> +<!ATTLIST Run PrescaleRD1 CDATA #IMPLIED> +<!ELEMENT LBRange EMPTY> +<!ATTLIST LBRange Start CDATA #REQUIRED> +<!ATTLIST LBRange End CDATA #IMPLIED> + diff --git a/Database/CoolRunQuery/html/atlas-runquery-help.css b/Database/CoolRunQuery/html/atlas-runquery-help.css new file mode 100644 index 00000000000..bc459382867 --- /dev/null +++ b/Database/CoolRunQuery/html/atlas-runquery-help.css @@ -0,0 +1,54 @@ +table.myhelptable { + table-layout: fixed; + margin: 0em 0em 0em 0; + background: white; + border-collapse: collapse; +} +table.myhelptable th.coltitle, table.myhelptable th.alltitle, table.myhelptable th.alldescr, table.myhelptable td { + border: 2px white solid; + padding: 4px; + vertical-align: top; +} +table.myhelptable th { + text-align: left; + background: gainsboro; + padding: 4px; + vertical-align: top; +} +table.myhelptable td, td.t1, td.t2 td.out1 td.out2 { + font-size: 80%; +} + +table.myhelptable caption { + margin-left: inherit; + margin-right: inherit; +} + +table.mysmallhelptable { + table-layout: fixed; + margin: 0em 0em 0em 0; + background: white; + border-collapse: collapse; +} +table.mysmallhelptable th.coltitle, table.mysmallhelptable th.alltitle, table.mysmallhelptable th.alldescr, table.mysmallhelptable td { + border: 2px white solid; + padding: 4px; + vertical-align: top; +} +table.mysmallhelptable th { + text-align: left; + background: gainsboro; + padding: 4px; + vertical-align: top; +} +table.mysmallhelptable td { + color: #555555; +} +table.mysmallhelptable td, td.t1, td.t2 td.out1 td.out2 { + font-size: 80%; +} +table.mysmallhelptable caption { + margin-left: inherit; + margin-right: inherit; +} + diff --git a/Database/CoolRunQuery/html/atlas-runquery-help.html b/Database/CoolRunQuery/html/atlas-runquery-help.html new file mode 100644 index 00000000000..10a0a8ebee2 --- /dev/null +++ b/Database/CoolRunQuery/html/atlas-runquery-help.html @@ -0,0 +1,84 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html xmlns:my> +<head> +<title>ATLAS Run Query − Help</title> + +<LINK href="atlas-runquery-help.css" rel="stylesheet" type="text/css"> + +<body style="background-color: #ffffff;"> + +<table class="mytable"><tr><th class="coltitle" style="font-family: sans-serif; font-size: 100%; background: #6D7B8D; color:white; height: 40px; vertical-align: middle">ATLAS Run Query − Help</th> +</tr><tr> + <td height="10"</td></tr><tr> + <td><b>Query format:</b> <br> + <ul> + <table class="mysmallhelptable"> + <tr> + <th colspan="2"><b>f(ind) [<i>condition1</i> and <i>condition2</i> and ...] / sh(ow) [<i>tag1</i> and <i>tag2</i> and ...] [/ other options]</b> </td> + </tr><tr> + <td colspan="2" height="3"></td> + </tr><tr> + <td width="100" valign="top"> <i>find</i> conditions:</td><td> Composed of <i>tag</i> and <i>argument</i>, where any of the tags defined in table below can be used. <br> + By default is used: "partition ATLAS and ftag data08*".</td> + </tr><tr> + <td valign="top"> <i>show</i> tags:</td><td> By default: run number, #lumi blocks (LB) and #events are shown for the selected runs.<br> + More tags (any of the list below) can be added. Note however that adding more tags will require + more data to be read from the DB, which increases the query delay (the same is true for the <i>find</i> conditions). </td> + </tr><tr> + <td valign="top">Other options:</td><td>Switch off use of default conditions by adding "/ nodef" to the end of the query.</td> + </tr><tr> + <td valign="top">Abbreviations:</td><td>Text in paranthesis can be dropped. <br> + For example: "f" is equal to "find", "sh" is equal to "show", "r" is equal to "runs" (cf. tags below), etc.</td> + </tr> + </table> + </ul> + </td> +</tr><tr> + <td height=10 valign="bottom"><b>Available <i>find</i> and <i>show</i> tags:</b></td> +</tr><tr> + <td height=7 valign="bottom"><b></b></td> +</tr><tr> + <td> +<ul> + <table class="myhelptable"> + <tr> + <th width="80"> Tag</th><th>Format examples</th><th>Description</th> + </tr><tr> + <td class="t1"> r(uns) </td><td class="t1"> "run 91000", "runs 91000-92000", <br>"runs 91000-(+)"(this run and all before (after)) </td><td class="t1">Run number</td> + </tr><tr> + <td class="t2"> t(ime) </td><td class="t2"> "time 10.9.2008-30.9.2008", <br>"time 15.10.2008-(+)" (this date and all before (after)) </td><td class="t2"> Date and time of run range. <br> <font color="red">Note that the time is the CTP (LB1_start -> LBLast_end) time corresponding to the genuine running time where triggers were accepted, and NOT the Run Control time.</font> </td> + </tr><tr> + <td class="t1"> dur(ation) </td><td class="t1"> "10d/h/m/s+/-", e.g., "2000s+" (run duration more than 2000 sec), <br> "3h-" (less than 3 hours) </td><td class="t1"> Run duration (see remark on time above)</td> + </tr><tr> + <td class="t2"> ftag </td><td class="t2"> "ftag data08_cos,data08_cosmag",<br> "ftag data08_cos*" (wildcard can be * or %)</td><td class="t2">ProjectTag in dataset name (denoted "filenameTag" in COOL) </td> + </tr><tr> + <td class="t1"> p(artition) </td><td class="t1"> "partition ATLAS" </td><td class="t1">DAQ partition (ATLAS for combined runs)</td> + </tr><tr> + <td class="t2"> smk </td><td class="t2"> "smk 398", "smk 398,399"<br> (format like for run ranges)</td><td class="t2"> Super-master-key labelling trigger menu </td> + </tr><tr> + <td class="t1"> trigk(eys) </td><td class="t1"> "show trigkeys"<br> (attribute for "show" only)</td><td class="t1"> Show super-master-key labelling trigger menu as well as L1 and HLT prescale keys and HLT release </td> + </tr><tr> + <td class="t2"> tr(igger) </td><td class="t2"> "trigger EF_e5* and tr L1_EM13I", <br>"sh trigger L2_E*,L2_Cosmic*" (wildcard can be * or %)</td><td class="t2"> Select and show trigger chains ("sh tr" displays all chains for all trigger levels)</td> + </tr><tr> + <td class="t1"> ev(ents) </td><td class="t1"> "events 10000+", "events 10000-", ... (same as for run number)</td><td class="t1"> Number of events (event filter accepts) in run</td> + </tr><tr> + <td class="t2"> alle(vents) </td><td class="t2"> "show allevents" <br> (attribute for "show" only)</td><td class="t2"> Show all event numbers, i.e., at EF (default), SFO (from inclusive streaming), L1 and L2 levels </td> + </tr><tr> + <td class="t1"> m(agnet) </td><td class="t1"> "magnet s(olenoid)", "magnet t(oroid)", "not magnet t(oroid)", ... </td><td class="t1"> Magnetic field configuration</td> + </tr><tr> + <td class="t2"> det(ector) </td><td class="t2"> "detector Pixel B" (for Pixel Barrel), <br>"detector Pixel" (for Pixel B & EC), <br>"all" (for all detectors, probed detector mask is 72551459979255)</td><td class="t2"> Detectors participating in run</td> + </tr><tr> + <td class="t1"> dq </td><td class="t1"> "dq [<i>flag</i>] <i>status</i> [<i>folder</i>]", where the DQ status is "g(reen)", "y(ellow)", "r(ed)", "u(known)", example: dq PIXB y+ (means yellow or green status) </td><td class="t1"> Data quality (DQ) status flags per system</td> + </tr><tr> + <td class="t2"> st(ream) </td><td class="t2"> "st physics_*", "st *IDCos*", may also select <i>show</i> fields, such as: "sh st *RPC*" </td><td class="t2"> Raw data stream (incl. physics and calibration)</td> + </tr><tr> + <td class="t1"> all </td><td class="t1"> "show all" </td><td class="t1"> Show all available information except for trigger; to also show trigger chains, type "show all and trigger"</td> + </tr> + </table> + </ul> +</ul><hr> +<font color="#555555"><font size=-2> +<i>Contact and support: <a href="mailto:andreas.hoecker@cern.ch"><i>Andreas Hoecker</i></a>, <a href="mailto:joerg.stelzer@cern.ch"><i>Joerg Stelzer</i></a> </font></font></td></tr> +</table> +</body> +</html> diff --git a/Database/CoolRunQuery/html/atlas-runquery-lb.css b/Database/CoolRunQuery/html/atlas-runquery-lb.css new file mode 100644 index 00000000000..3aeba800e0b --- /dev/null +++ b/Database/CoolRunQuery/html/atlas-runquery-lb.css @@ -0,0 +1,56 @@ +table.outer +{ + font-family: sans-serif; + font-size: 90%; + padding: 5px; +} + +table.outer td +{ + text-align: left; +} + +table.lb +{ + width: auto; + border: 0px solid; + border-width: 0px; + margin: 0 0 0 0; + border-spacing: 0px; + border-collapse: separate; + padding: 0px; + font-size: 90%; +} + +table.lb th { + padding-left: 10px; + font-family: sans-serif; + font-weight: bold; + text-align: center; + vertical-align: bottom; + border-right-width: 20px; border-right-style: solid; border-color: white; +} + +table.lb thead tr.second th { + padding-left: 10px; + font-family: sans-serif; + font-weight: normal; + font-size: 80%; + text-align: center; + vertical-align: bottom; + border-right-width: 20px; border-right-style: solid; border-color: white; +} + + +table.lb td { + font-family: sans-serif; text-align: right; font-size: 90%; padding-left: 5px; padding-right: 5px; +} + + +table.lb td.dt { + font-size: 80%; font-style: italic; + border-right-width: 10px; border-right-style: solid; border-color: white; +} + + + diff --git a/Database/CoolRunQuery/html/atlas-runquery-maintanancenote.html b/Database/CoolRunQuery/html/atlas-runquery-maintanancenote.html new file mode 100644 index 00000000000..d404ad3d229 --- /dev/null +++ b/Database/CoolRunQuery/html/atlas-runquery-maintanancenote.html @@ -0,0 +1,80 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> + <head> + <title>Atlas Runquery Maintenance Note</title> + <link rel="stylesheet" type="text/css" href="atlas-runquery.css" media="screen"> + </head> + + <body> + + <table class="toptable"> + <tr style="height:45px; vertical-align:top;"> + <td><font size="+1"><b>ATLAS Run Queries</b></font> </td> + <td style="padding-right: 0px;"> + <table style="border-spacing:6px; margin-left:auto; margin-right:12px"> + <tr> + + <td class="dontPrint" style="border: 1px white solid; padding: 0.3em; width: 56px; text-align:center; font-size: 100%; vertical-align: middle;"> + <a href="mailto:andreas.hoecker@cern.ch,joerg.stelzer@cern.ch" title="Contact and support"> + Contact + </a> + </td> + <td class="dontPrint" style="border: 1px white solid; padding: 0.3em; width: 35px; text-align:center; font-size: 100%"> + <a href="javascript:helppopup()" title="Help on Run Query Formatting"> + Help + </a> + </td> + </tr> + </table> + </td> + </tr> + <tr style="height:20px"> + <td colspan="2"> + <table class="toptableLinks"> + <tr> + <td><a href="http://pcatdwww.cern.ch/atlas-point1/wmi/current/Run%20Status_wmi/index.html">Current run</a></td> + <td><a href="https://atlas.web.cern.ch/Atlas/GROUPS/DATAPREPARATION/">Run Summaries</a></td> + <td><a href="http://trigconf.cern.ch/">Trigger Configuration Query</a></td> + <td><a href="http://ami.in2p3.fr:8080/AMI/servlet/net.hep.atlas.Database.Bookkeeping.AMI.Servlet.Command?linkId=512">AMI Data Search</a></td> + <td><a href="http://dashb-atlas-data.cern.ch/dashboard/request.py/site">DDM Dashboard</a></td> + <td><a href="http://atlas.web.cern.ch/Atlas/tzero/prod1/monitoring/">Tier-0 Monitoring</a></td> + <td><a href="http://atlasdqm.web.cern.ch/atlasdqm/">DQ Monitoring</a></td> + <td><a href="https://twiki.cern.ch/twiki/bin/view/Atlas/DataPreparation">Data Preparation</a></td> + <td><a href="http://pcatdwww.cern.ch/atlas-point1/operation.php">Operations</a></td> + </tr> + </table> + </td> + </tr> + </table> + + <table> + <tr><td width="40px"><td height="100px"> + <font style="color: darkblue"><strong>The ATLAS Runquery page is currently being maintained and will be back shortly.</strong></font> + </td></tr> + <tr height="500px"></tr> + </table> + <table class="bottomtable"> + <tr style="height:45px; vertical-align: top;"> + <td> + <i>Contact and support: + <a href="http://consult.cern.ch/xwho/people/400557">Andreas Hoecker</a>, + <a href="http://consult.cern.ch/xwho/people/652897">Jörg Stelzer</a> + </i> + </td> + <td><i>Maintenance</i></td> + <td style="text-align:right"><i> + <script language="JavaScript" type="text/javascript"> + var date = document.lastModified.toString(); + if (date == "") { // unknown date (or January 1, 1970 GMT) + document.writeln("Last Modified: Unknown") + } else { + document.writeln ("Last modified: " + date); } + </script> + </i></font> + </td> + </tr> + </table> + + + </body> +</html> diff --git a/Database/CoolRunQuery/html/atlas-runquery-print.css b/Database/CoolRunQuery/html/atlas-runquery-print.css new file mode 100644 index 00000000000..bd2d4f970f6 --- /dev/null +++ b/Database/CoolRunQuery/html/atlas-runquery-print.css @@ -0,0 +1,33 @@ + +table.toptableLinks { display:none; } + +.TabView, .dontPrint { display:none; } + +table.mytable { + table-layout: fixed; + margin: 0em 0em 0em 0; + margin-left: 4px; + border: 0px white solid; + padding: 0px; + background: white; + border-collapse: collapse; +} + +table.mytable th { text-align: left; } + +table.bottomtable +{ + position: relative; + width: 100%; + top: auto; + bottom: 0; + left: 0; + right: auto; + margin: 0 0 0 0; + padding-top: 0px; + padding-left: 12px; + padding-right: 20px; + color: #000000; + background-repeat: no-repeat; + font-size: 85%; +} diff --git a/Database/CoolRunQuery/html/atlas-runquery-relnotes.html b/Database/CoolRunQuery/html/atlas-runquery-relnotes.html new file mode 100644 index 00000000000..d474e3bac62 --- /dev/null +++ b/Database/CoolRunQuery/html/atlas-runquery-relnotes.html @@ -0,0 +1,121 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html xmlns="my"> + <head> + <title>ATLAS Run Query - Change logs</title> + + <LINK href="atlas-runquery.css" rel="stylesheet" type="text/css"> + + <body style="background-color: #ffffff;"> + + <table class="relnotetable"> + <thead> + <tr><th class="coltitle" style="font-family: sans-serif; font-size: 100%;">Change Logs − Features and Fixes</th></tr> + </thead> + <tbody> + <tr> + <td height=40 valign="bottom"><i>Recent items appended to the top (significant features or bug fixes only):</i></td> + </tr> + <tr height="20"></tr> + <!-- next --> + <tr> + <td> + <i>3-Jan-2009:</i> + <ul> + <li> Tabbed examples</li> + <li> Improved help pages</li> + </ul> + </td> + </tr> + <tr> + <td> + <i>20-Dec-2008:</i> + <ul> + <li> More involved DQ-flag <i>find</i> queries and <i>show</i> patterns (see also notes from 07-Dec-2008)</li> + <li> Significant query speed up</li> + <li> Access to SFO database for stream queries, and reliable numbers of events per stream</li> + <li> Summary (number od runs, events, etc) for selected runs</li> + <li> TH1 plots for all numerical query results (see thumbnails at bottom of page after query)</li> + </ul> + </td> + </tr> + <!-- next --> + <tr> + <td> + <i>07-Dec-2008:</i> + <ul> + <li> Extented dataquality browsing using keyword <i>any</i> </li> + <li> Query for runs with unset dataquality using dq flag <i>n.a.</i></li> + <li><i>Example: f r 90270-90350 and dq any pix n.a.</i></li> + </ul> + </td> + </tr> + <!-- next --> + <tr> + <td> + <i>04-Dec-2008:</i> + <ul> + <li> ROOT file with TTree for download is created from serialised result dictionary</li> + </ul> + </td> + </tr> + <!-- next --> + <tr> + <td> + <i>03-Dec-2008:</i> + <ul> + <li> Speedup searches (more than 2 times faster)</li> + <li> Add possibility to search for runs where no DQ is defined e.g. dq PIXB n.a.</li> + <li> Added display of number of lumiblocks</li> + </ul> + </td> + </tr> + <!-- next --> + <tr> + <td> + <i>28-Nov-2008:</i> + <ul> + <li> Added link to serialised ("pickled") version of python dictionary with full result content</li> + <li> Bug fixes: + <ul> + <li> Fixed mismatch in LB IOV for DQ: LB starts with 1 and not 0</li> + </ul> + </li> + </ul> + </td> + </tr> + <!-- next --> + <tr> + <td> + <i>26-Nov-2008:</i> + <ul> + <li> Added direct links to ELOG (run-wise search) and to DQ browser in run result table</li> + <li> Bug fixes: + <ul> + <li> Fixed problem in "show dq [flag] [COOL-folder]" (thanks to Peter Cwetanski for reporting this) + <li> Fixed problem in time-based query (thanks to Alexander Mann for reporting this) + </ul> + </ul> + </td> + </tr> + <!-- next --> + <tr> + <td> + <i>25-Nov-2008:</i> + <ul> + <li>First release</li> + </ul> + </td> + </tr> + <!-- end --> + <tr> + <td style="color: #555555; font-size: 60%;"> + <hr> + <i>Contact and support: + <a href="mailto:andreas.hoecker@cern.ch"><i>Andreas Hoecker</i></a>, + <a href="mailto:joerg.stelzer@cern.ch"><i>Joerg Stelzer</i></a> + </td> + </tr> + </tbody> + </table> + </body> +</html> diff --git a/Database/CoolRunQuery/html/atlas-runquery-results.css b/Database/CoolRunQuery/html/atlas-runquery-results.css new file mode 100644 index 00000000000..e851584c785 --- /dev/null +++ b/Database/CoolRunQuery/html/atlas-runquery-results.css @@ -0,0 +1,158 @@ +table.outer +{ + font-family: sans-serif; + font-size: 90%; + padding: 5px; +} + +table.outer td +{ + text-align: left; +} + +table.resulttable { + table-layout: auto; + font-family: sans-serif; + margin: 0em 0em 0em 0; + background: white; + border-collapse: collapse; + border: 2px white solid; + padding: 4px; + vertical-align: top; +} + +table.resulttable th { + font-size: 80%; + text-align: center; + background: #4F81BD; + color: white; + padding: 4px; + border: 2px white solid; + vertical-align: top; +} + +table.resulttable th.rowsum { + font-size: 80%; + text-align: center; + background: gainsboro; + color: black; + padding: 4px; + border: 2px white solid; + vertical-align: top; +} + +table.resulttable th.colsum { + font-size: 65%; + text-align: center; + background: #4F81BD; + color: white; + padding: 4px; + border: 2px white solid; + vertical-align: top; +} + +table.resulttable th.colsumL { + font-size: 80%; + text-align: center; + background: #4F81BD; + color: white; + padding: 4px; + border: 2px white solid; + vertical-align: top; +} + +table.resulttable th.emptycorner { + font-size: 80%; + text-align: left; + background: #f0f0f0; + color: #777777; + padding: 4px; + border: 2px white solid; + vertical-align: top; +} + +table.resulttable td { + border: 2px white solid; + text-align: left; + padding: 4px; + vertical-align: top; +} + +table.resulttable td.tdna { background: #f2f2f2; color: #bbbbcc; } +table.resulttable td.tdNA { background: #cccccc; color: black; } +table.resulttable td.tdU { background: #3090C7; color: white; } +table.resulttable td.tdR { background: #E42217; color: #FFFFFF; } +table.resulttable td.tdY { background: #FFF380; color: black; } +table.resulttable td.tdG { background: #8FFB17; color: black; } +table.resulttable td.tdB { background: #444444; color: #FFFFFF; } +table.resulttable td.td { text-align: center; } +table.resulttable td.tdsmallw { min-width: 45px; text-align: left; border-width: 0px; padding: 1px; } +table.resulttable td.tdsmall { text-align: left; border-width: 0px; padding: 1px; } +table.resulttable .tooltip { cursor: pointer; } +table.resulttable td.stream { cursor: pointer; text-decoration: underline; text-align: right; color: rgb(0, 0, 100); } +table.resulttable td.def { padding: 2px; height: 100%; } + +table.resulttable table.dqdefects { width: 100%; border-left: medium solid #E42217; border-collapse: collapse; font-size: 80%; text-align:left; height: 100%; } +table.resulttable table.dqdefects td { border: 0px white solid; color: #888888; padding-right: 5px; padding-left: 3px; padding-top: 0; padding-bottom: 0; font-weight: normal; } + +<!-- +table.resulttable table.dqdefects td.intolerable { font-weight: bold; color: black;} +table.resulttable table.dqdefects td.lb { vertical-align: middle; padding: 0; border: 0px white solid; color: blue; font-size: 85%; max-width: 130px;} +--> + +table.resulttable td.intolerable { font-weight: bold; color: black;} +table.resulttable td.lb { vertical-align: middle; padding: 0; border: 0px white solid; color: blue; font-size: 85%; max-width: 130px;} + + +table.triggerlinktable { font-size: 85%; border-width: 0px; border-spacing: 0px; border-collapse: collapse; } +table.triggerlinktable th { padding: 0.2em; font-weight: bold; font-family: roman; border-width: 0px;} +table.triggerlinktable td { padding: 0.08em; border: 0px white solid; text-align:right; border-width: 0px; font-size: 90%; } + + +table.bcidtable { font-size: 85%; border-width: 0px; border-spacing: 0px; border-collapse: collapse; } +table.bcidtable th { padding: 0.2em; font-weight: bold; font-family: roman; border-width: 0px; } +table.bcidtable td { padding: 0.2em; border-top: 0px white solid; border-bottom: 1px #C0C0D0 solid; border-left: 0px white solid; border-right: 0px white solid; text-align:right; } +table.bcidtable td.td1 { padding: 0.2em; border-top: 0px #eeeeee solid; border-bottom: 1px #C0C0D0 solid; border-left: 0px white solid; border-right: 0px white solid; text-align:right; } +table.bcidtable td.td2 { padding: 0.2em; border-top: 0px #dddddd solid; border-bottom: 1px #C0C0D0 solid; border-left: 0px white solid; border-right: 0px white solid; text-align:right; } +table.bcidtable tr.tr0 { background: #555555; color: white; height: 20px } +table.bcidtable tr.tr1 { background: #f3f3f3; } +table.bcidtable tr.tr2 { background: #e3e3e3; } + + +table.datasettable { font-size: 85%; border-width: 0px; border-spacing: 0px; border-collapse: collapse; } +table.datasettable th { padding: 0.2em; color: #FFFFFF; font-weight: bold; font-family: roman; background-color: #98AFC7; border-width: 0px; } +table.datasettable td { padding: 0.2em; border: 0px white solid; text-align:left; border-width: 0px; } + +table.ratestable { font-size: 65%; border-width: 0px; border-spacing: 0px; border-collapse: collapse; } +table.ratestable td { padding: 0.0em; border: 0px white solid; text-align:left; border-width: 0px; } + + +table.resulttabletext +{ + font-family: sans-serif; + table-layout: fixed; + margin: 0em 0em 0em 0; + background: white; + border-collapse: collapse; + border: 2px white solid; + padding: 4px; + vertical-align: top; + text-align: left; + font-size: 100%; +} + +table.resulttabletext th +{ + text-align: left; + background: white; + padding: 4px; + vertical-align: top; +} + +table.resulttabletext td +{ + border: 2px white solid; + text-align: left; + padding: 4px; + vertical-align: top; +} diff --git a/Database/CoolRunQuery/html/atlas-runquery-tabview.css b/Database/CoolRunQuery/html/atlas-runquery-tabview.css new file mode 100644 index 00000000000..509769b176f --- /dev/null +++ b/Database/CoolRunQuery/html/atlas-runquery-tabview.css @@ -0,0 +1,38 @@ + +div.TabView div.Tabs { + height: 24px; +} + +div.TabView div.Tabs a { + float: left; + border-top: #c0c0c0 1px dotted; + border-left: #c0c0c0 1px dotted; + display: block; + text-align: center; + height: 24px; + line-height: 28px; + vertical-align: middle; + background: #eeeeee; + text-decoration: none; + font-weight: 700; + font-size: 12px; + color: #000080; +} + +div.TabView div.Tabs a:hover, div.TabView div.Tabs a.Active { + background: #C6DEFF; +} + +div.TabView div.Pages { + clear: both; + border: 1px solid #909090; +} + +div.TabView div.Pages div.Page { + height: 100%; + padding: 0px; +} + +div.TabView div.Pages div.Page div.Pad { + padding: 10px 18px; +} diff --git a/Database/CoolRunQuery/html/atlas-runquery.css b/Database/CoolRunQuery/html/atlas-runquery.css new file mode 100644 index 00000000000..d81b2ffb065 --- /dev/null +++ b/Database/CoolRunQuery/html/atlas-runquery.css @@ -0,0 +1,411 @@ +body { margin:0 0 0; padding:0; background-color: #ffffff;} + +table.bodytable +{ + height:100%; + vertical-align:top; + padding: 0px; + margin-left: 7px; + margin-right: 0px; + padding-top: 0px; + padding-bottom: 0px; + width:100% +} + +td { vertical-align: middle; text-align: left; font-family: sans-serif; font-size: 90%; } +th { vertical-align: bottom; text-align: center; font-family: sans-serif; font-weight: bold; font-size: 90%; } + +td.option { width: 160px; } +td.array { width: 50px; } +td.val { width: 120px; } +td.predef { width: 150px; } +td.info { width: 320px; } + +p.pgm { font-family: sans-serif } +td.pgm { font-family: sans-serif; color: #222222; vertical-align: top; font-size: 87%; width: 50%; background: white; cursor: pointer } +td.pgm:hover { text-decoration:underline; } +td.cmt { font-family: sans-serif; color: #444444; font-size: 80%; vertical-align: top; background: white; } +td.space { background: white; height: 10px; } + +th.alltitle { color: white; background-color: #2B3856; padding: 0 } +th.alldescr { font-weight: normal; color: #222222; background-color: white; font-size: 80%; } +th.coltitle { color: black; background-color: #98AFC7; } +td.helptd { font-weight: normal; color: #000000; background-color: white; font-size: 130%; } + +table.toptable +{ + position: relative; + width: 100%; + top: 0; + bottom: auto; + left: 0; + right: auto; + margin: 0 0 0 0; + padding-top: 0px; + color: #ffffff; + background-repeat: no-repeat; + background-color: #5D6B7D; +} + +table.bottomtable +{ + position: relative; + width: 100%; + top: auto; + bottom: 0; + left: 0; + right: auto; + margin: 0 0 0 0; + padding-top: 0px; + color: #ffffff; + background-repeat: no-repeat; + background-color: #5D6B7D; + font-size: 85%; +} + +#footer { + position:absolute; + background-color: #eee; + color: #000; + margin: 0em 0 0 0; + padding: .7em; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + font-size: 80%; + height:100%; + clear: both; +} + +table.bottomtable td +{ + padding-left: 12px; + padding-right: 15px; +} + +table.bottomtable a +{ + color:white; + font-style: italic; +} + +table.toptable a +{ + color:white; + font-size: 75%; +} + +table.toptable td +{ + text-align: left; + padding-left: 10px; +} + +table.toptableLinks +{ + margin-left: auto; + margin-right: 10px; + padding-top: 0px; + padding-bottom: 0px; + padding-left: 0px; + padding-right: 0px; + color: #ffffff; + background-repeat: no-repeat; +} + +table.toptableLinks td +{ + text-align: right; + border: 0px white solid; + padding: 0.4em; +} + +hr.horiline +{ + width: auto; + color:red; + background-color: #6D7B8D; + height:1px; + margin-left: 0px; + margin-right: 0px; + border:0; +} + +table.relnotetable { + table-layout: fixed; + margin: 0em 0em 0em 0; + background: white; + border-collapse: collapse; +} +table.relnotetable td { font-size: 80%; } + +table.mytable { + table-layout: fixed; + margin: 0em 0em 0em 0; + margin-left: 4px; + margin-top: 0px; + border: 0px white solid; + padding: 0px; + background: white; + border-collapse: collapse; +} +table.mytable th.coltitle, table.mytable th.alltitle, table.mytable th.alldescr, table.mytable td { + border: 2px white solid; + padding: 0.2em; +} +table.mytable th { + text-align: left; + font-size: 100%; +} +table.mytable caption { + margin-left: inherit; + margin-right: inherit; +} + + +table.exampletable { + width: 100%; + table-layout: fixed; + margin: 0em 0em 0em 0; + background: white; + padding: 0px; + border-width: 0px; + border-color: white; + border-style: solid; + border-collapse: collapse; + font-size: 110%; +} + +table.exampletable a {text-decoration:none; color: #222222} +table.exampletable a:hover { text-decoration: underline; font-style: normal; color: #222222 } + +td.t1 { + background: #B0FA91; +} +td.t2 { + background: #D9FAC9; +} + +tr.outtitlerun { + background: gainsboro; + font-size: 80%; + width: 80px; +} +tr.outtitleother { + background: lavender; + font-size: 80%; +} +tr.sum { + background: #FFFaa0; + font-size: 90%; + color: #000000; + text-align: right; +} +tr.space { + background: #ffffff; + font-size: 90%; +} + +tr.out2 { background: #E9EDF4; font-size: 90%; } +tr.out1 { background: #D0D8E8; font-size: 90%; } + +tr.outRed1 { background: #FFDFDF; font-size: 90%; } +tr.outRed2 { background: #F1C8CE; font-size: 90%; } + +tr.out1LBR { background: #F0F0F0; font-size: 70%; visibility: collapse; } +tr.out2LBR { background: #F5F5F5; font-size: 70%; visibility: collapse; } + + +tr.showmiss1, td.showmiss1 { + background: #E9EDF4; + font-size: 90%; +} +tr.showmiss2, td.showmiss2 { + background: #D0D8E8; + font-size: 90%; +} +tr.selmiss1, td.selmiss1 { + background: lavender; + color: red; + font-size: 90%; +} +tr.selmiss2, td.selmiss2 { + background: #B0C4DE; + color: red; + font-size: 90%; +} + +table.othertable { + table-layout: auto; + font-family: sans-serif; + margin: 0em 0em 0em 0; + background: white; + border-collapse: collapse; + border: 2px white solid; + padding: 5px; + vertical-align: top; + margin-left: 13px; +} + +table.othertable th { + font-size: 80%; + text-align: center; + background: #4F81BD; + color: white; + padding: 4px; + border: 2px white solid; + vertical-align: top; +} + +table.othertable td { + border: 2px white solid; + text-align: left; + padding: 4px; + vertical-align: top; + background: #E9EDF4; + font-size: 80%; +} + + +a {text-decoration:none} +:link { color: rgb(0, 0, 100) } /* for unvisited links */ +:visited { color: rgb(0, 0, 100) } /* for visited links */ +a:active { color: rgb(0, 0, 100) } /* when link is clicked */ +a:hover { text-decoration: underline; font-style: normal; color: rgb(0, 0, 150) } + +a.info{ + position:relative; /*this is the key*/ + font-size: 11px; + background-color:#ffffff; + color:#777777; + text-decoration:none; + font-weight: lighter +} + +a.info:hover{ + background-color:#ffffff; + color:#aaaaaa; + font-weight: lighter +} + +a.info span{ + display: none +} + +a.info:hover span{ /*the span will display just on :hover state*/ + display:block; + font-size: 12px; + position:absolute; + top:-5em; left:0em; width:37em; + border:1px solid #667295; + background-color:#E5E9F5; + color:#000000; + text-align: left; + font-weight: lighter; + padding: 5px +} + +div#tipDiv +{ + color:#000; + font-family: sans-serif; + line-height:1.2; + font-size:11px; + background-color:#E1E5F1; + border:1px solid #667295; + padding:5px; + text-align: left; + font-weight: lighter +} + +div.tooltip +{ + font-size:11px; + width:23em; +} + +div.tip2 +{ + width:37em; +} + +table.defaulttable +{ + padding: 0; + font-size: 120%; + table-layout: fixed; +} + +table.streamtiptable +{ + height:100%; + vertical-align:top; + padding: 0px; + margin-left: 0px; + margin-right: 0px; + padding-top: 0px; + padding-bottom: 0px; + table-layout: auto; +} + +table.streamtiptable td { vertical-align: middle; text-align: left; font-family: sans-serif; font-size: 100%; } +table.streamtiptable th { vertical-align: bottom; text-align: center; font-family: sans-serif; font-weight: bold; font-size: 100%; } + +table.pileuptable { + width: auto; + border: 0; + border-width: 0px; + margin: 0 0 0 0; + border-spacing: 0px; + border-collapse: separate; + padding: 0px; + font-size: 100% +} + +table.pileuptable th { + border-width: 0.1em; background: #d9dDe4;; color:#000000; + vertical-align: middle; text-align: center; font-family: sans-serif; font-size: 100%; +} +table.pileuptable td { + border-width: 0.1em; + vertical-align: middle; text-align: center; font-family: sans-serif; font-size: 100%; +} + +table.eventsperlbstreamtable +{ + padding: 0; + font-size: 120%; + table-layout: auto; +} + + +table.overlaptable +{ + color: #222222; + border-width: 0; + border-spacing: 0; + border-color: #E1E5F1; + background-color: #E1E5F1; + padding: 0; + table-layout: auto; + margin: 0em 0em 0em 0; + border-collapse: collapse; +} + +table.overlaptable td.tdov1 +{ + border-width: 0; + padding: 2px; + border-color: #E1E5F1; + background-color:#E1E5F1; +} + +table.overlaptable td.tdov2 +{ + border-width: 0; + padding: 2px; + border-color: #C1C5E1; + background-color:#C1C5E1; +} + + + diff --git a/Database/CoolRunQuery/html/atlas-runquery.html b/Database/CoolRunQuery/html/atlas-runquery.html new file mode 100644 index 00000000000..1485e4fcc12 --- /dev/null +++ b/Database/CoolRunQuery/html/atlas-runquery.html @@ -0,0 +1,252 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> + <head> + <title>ATLAS Run Query</title> + + <meta http-equiv="content-type" content="text/html; charset=ISO-8859-1"></meta> + <meta name="ATLAS_Run_Query" content="ATLAS Run Query"></meta> + + <link rel="stylesheet" type="text/css" href="html/atlas-runquery.css" media="screen"></link> + <link rel="stylesheet" type="text/css" href="html/atlas-runquery-results.css" media="screen"></link> + <link rel="stylesheet" type="text/css" href="html/atlas-runquery-tabview.css" media="screen"></link> + <!--link rel="stylesheet" type="text/css" href="atlas-runquery-print.css" media="print"--> + <link rel="shortcut icon" href="html/atlas1.ico"></link> + + <script type="text/javascript" src="js/switcher.js"></script> + <script type="text/javascript" src="js/tabView.js"></script> + + <script type="text/javascript" src="js/dw_event.js"></script> + <script type="text/javascript" src="js/dw_viewport.js"></script> + <script type="text/javascript" src="js/dw_tooltip.js"></script> + <script type="text/javascript" src="js/dw_tooltip_aux.js"></script> + + <script type="text/javascript" src="js/rq.js"></script> + <script type="text/javascript" src="js/rq_examples.js"></script> + <script type="text/javascript" src="js/rq_simplepopups.js"></script> + + <script type="text/javascript" src="js/jquery.min.js"></script> + <script type="text/javascript" src="js/jquery.popupWindow.js"></script> + <script type="text/javascript"> + jQuery.fn.ratespopup = function() { + window.open(this[0].id,''+this[0].id, + 'height=800,width=1000,scrollbars=1,menubar=1').focus(); + } + + $(document).ready(function(){ + $(".ratespopup").click(function(){$(this).ratespopup();}); + }); + </script> + + <script type="text/javascript" src="js/animatedcollapse.js"></script> + <script type="text/javascript"> + animatedcollapse.addDiv('AtlRunQueryCmd', 'fade=1,speed=400') + animatedcollapse.ontoggle = function($, divobj, state){ //fires each time a DIV is expanded/contracted + } + animatedcollapse.init() + </script> + + </head> + + <body onload='i=document.getElementById("queryinput");i.focus();i.select();insertExamples()'> + + <table class="toptable"> + <tr style="height:45px; vertical-align:top;"> + <td><font size="+1"><b>ATLAS Run Queries</b></font> </td> + <td style="padding-right: 0px;"> + <table style="border-spacing:6px; margin-left:auto; margin-right:12px"> + <tr> + <td class="dontPrint" style="border: 1px white solid; padding: 0.3em; width: 56px; text-align:center; font-size: 100%; vertical-align: middle;"> + <a href="mailto:andreas.hoecker@cern.ch,joerg.stelzer@cern.ch" title="Contact and support"> + Contact + </a> + </td> + <td class="dontPrint" style="border: 1px white solid; padding: 0.3em; width: 35px; text-align:center; font-size: 100%"> + <a href="javascript:helppopup()" title="Help on Run Query Formatting"> + Help + </a> + </td> + </tr> + </table> + </td> + </tr> + <tr style="height:20px"> + <td colspan="2"> + <table class="toptableLinks"> + <tr> + <td><a href="http://pcatdwww.cern.ch/atlas-point1/wmi/current/Run%20Status_wmi/index.html">Current run</a></td> + <td><a href="https://atlas.web.cern.ch/Atlas/GROUPS/DATAPREPARATION/DataSummary/">Data Summary</a></td> + <td><a href="http://atlas-trigconf.cern.ch/">Trigger Configuration Query</a></td> + <td><a href="http://ami.in2p3.fr:8080/AMI/servlet/net.hep.atlas.Database.Bookkeeping.AMI.Servlet.Command?linkId=512">AMI Data Search</a></td> + <td><a href="http://dashb-atlas-data.cern.ch/dashboard/request.py/site">DDM Dashboard</a></td> + <td><a href="http://atlas.web.cern.ch/Atlas/tzero/prod1/monitoring/">Tier-0 Monitoring</a></td> + <td><a href="http://atlasdqm.web.cern.ch/atlasdqm/">DQ Monitoring</a></td> + <td><a href="https://twiki.cern.ch/twiki/bin/view/Atlas/DataPreparation">Data Preparation</a></td> + <td><a href="http://pcatdwww.cern.ch/atlas-point1/operation.php">Operations</a></td> + </tr> + </table> + </td> + </tr> + </table> + + <table class="mytable" width="95%"> + <tr> + <td style="width:100%;"> + <table class="mytable" width="100%"> + <tr> + <th height="40" valign="bottom" style="font-size: 14px;width:100%"> + Run Search − Insert Your Query: + <font style="font-size:12px;font-weight:100"> + <!--INSERTLINKCACHE--> + </font> + </th> + </tr> + <tr> + <td style="background-color:white"> + <form id="RunQueryInput" action="query.py" method="get" style="margin-bottom:0px"> + <input id="queryinput" name="q" size="105%" value="find run last 10 and ready / show all"/> + <input type="submit" value="Show Runs"/> + </form> + </td> + </tr> + <tr class="dontPrint"> + <td colspan="2" style="font-size: 11px; text-align: left;"><div class="showTip LX1" style="display:inline;cursor:pointer"> [ <i style="color:rgb(0, 0, 100)">Default query condition</i> ]</div> [ <font color="#666666"><i>Type 'f ... / show <b>all</b>' to see full info (except for DQ and trigger)</i></font> ]</td> + </tr> + </table> + + </td> + </tr> + <tr> + <td style="font-size:50%"> + </td> + </tr> + <!--START_EXAMPLES--> + <tr> + <td> + <table class="mytable dontPrint" width="100%"> + <tbody> + <tr> + <td height="20" valign="top"> + <font color="#111133"><i><font size="-1">Examples <font size="-2">(query format inspired by <a href="http://www.slac.stanford.edu/spires/hep/"><font color="#111133">SPIRES</font></a>)</font>:</font></i></font><br/> + </td> + </tr> + <tr> + <td> + <div class="TabView" id="TabView"> + <!-- *** Tabs ************************************************************** --> + <div class="Tabs"> + <a> Run / events </a> + <a> Time </a> + <a> Detectors </a> + <a> Streams </a> + <a> Magnets </a> + <a> Data quality </a> + <a> Trigger </a> + <a> DAQ </a> + <a> Datasets </a> + <a> Beamspot </a> + <a> LAr </a> + <a> Lumi </a> + <a> LHC </a> + <a style="border-right: #c0c0c0 1px dotted;"> <font color="#FF0000">Xtras</font> </a> + </div> + <!-- *** Pages ************************************************************* --> + <div class="Pages" style="width: 855px; height: 220px; text-align: left;"> + <div class="Page" id="exampleRunEvt"></div> + <div class="Page" id="exampleTime"></div> + <div class="Page" id="exampleDetectors"></div> + <div class="Page" id="exampleStreams"></div> + <div class="Page" id="exampleMagnets"></div> + <div class="Page" id="exampleDQ"></div> + <div class="Page" id="exampleTrigger"></div> + <div class="Page" id="examplePartition"></div> + <div class="Page" id="exampleDatasets"></div> + <div class="Page" id="exampleBeamSpot"></div> + <div class="Page" id="exampleLAr"></div> + <div class="Page" id="exampleLumi"></div> + <div class="Page" id="exampleLHC"></div> + <div class="Page" id="exampleOther"></div> + </div> + <!-- *** End of Examples ************************************************** --> + </div> + </td> + </tr> + <tr> + <td> + <i><font size="-1"><a href="javascript:helppopup()" title="Help on Run Query Formatting">(More formatting help)</a></font></i> + </td> + </tr> + </tbody> + </table> + <p></p> + </td> + </tr> + <!--END_EXAMPLES--> + </table> + <p></p> + + <!--INSERTQUERYRESULT--> + + <script language='javascript'> + function readAndEcho(pat) + { + fh = fopen(pat, 0); // Open the file for reading + if(fh!=-1) // If the file has been successfully opened + { + length = flength(fh); // Get the length of the file + str = fread(fh, length); // Read in the entire file + fclose(fh); // Close the file + + // Return the contents of the file + return str; + } + } + </script> + <table class="bottomtable"> + <tr style="height:45px; vertical-align: top;"> + <td> + <i>Contact and support: + <!--CONTACT_START--> + <a href="http://consult.cern.ch/xwho/people/400557">Andreas Hoecker</a>, + <a href="http://consult.cern.ch/xwho/people/652897">Jörg Stelzer</a> + <!--CONTACT_END--> + </i> + </td> + <td><i>CoolRunQuery-00-04-50</i></td> + <td style="text-align:right"><i> + <script language="JavaScript" type="text/javascript"> + var date = document.lastModified.toString(); + if (date == "") { // unknown date (or January 1, 1970 GMT) + document.writeln("Last Modified: Unknown") + } else { + document.writeln ("Last modified: " + date); } + </script> + </i></font> + </td> + </tr> + </table> + + + <script type="text/javascript"> + $('.externalWindow').popupWindow({ + scrollbars:1, + width:750 + }); + </script> + + + <script type="text/javascript"> + if(dw_Tooltip.content_vars==undefined) { + dw_Tooltip.content_vars = {}; + }; + dw_Tooltip.content_vars["LX1"] = { + content: '<table class="defaulttable"><tr><td>By default is added to your query: <font color="red">ptag data09*,data10*,data11*,data12* and part ATLAS</font>.</td></tr><tr><td>To switch this off append <font color="red">/ nodef</font> to query.</td></tr><tr><td> For example: <font color="red">f r 114000-114300 / sh ptag / nodef</font>.</td></tr></table>', klass: 'tip2' + }; + </script> + + <!-- StatCounter has been removed --> + + <script type="text/javascript"> + tabview_initialize('TabView'); + </script> + </body> +</html> diff --git a/Database/CoolRunQuery/html/atlas1.ico b/Database/CoolRunQuery/html/atlas1.ico new file mode 100644 index 0000000000000000000000000000000000000000..57fa129e4be36c0e6d8022af160d87075aabbabe GIT binary patch literal 894 zcmb7DZAep57{1Zph*{AG{V9L+4N(w6DcPT>Fw!7UNDNH;A`Ay=$|Pw&hHZ1EwKZ@$ zGtw>0>1-|C(wfd~?(Wm+bZ$1MXwGfAd3Abs?45#`;*XxoIXve%&->haJ}!psgNH)F zpg)e~?8mU97>1pL1`>7(T2$Wv%<aVbKS1uOIgapWDO;LX#?2~z_q2qcUEWOP=xDWS zZj0LMTi9+#2>AlR&z<Tr=BJqR>5p<>RoAr@(HqVt<nTUrOO-f5tZesha<q;j2qoum zZzdJ2uC0kB0|#R>REA+l+ii|2P=HW;3WIVY@nK%|lS09<xC$muyr+L7KPi`WBsS&r zrL?QJi|*xD*<Dk6`5~T+piG+Zz~So`ucg*DNXCA9z`XlwK%4dYRa$F)$@@cR(qc~D zxp3ogAw$4zYIjf1Y_9-5Hy@~cD@n~_+)B#69QUA@UUMZeqqgx&b`dM{`OEmbX-10; zZ19>uFr@4=Cft8UEoZgMdtT7FwozwzjTCqE`WAu~$AktSPEM!((ip%ETURL%XXKXR zW}D9Dpm9EE_2x-WbaepxxXbJJ195w1Dqiz|5G�%BEIn*PziRR%nRjWe|g*CCku= z7Pqu@cEJ!<3LS&ZTM36DzouU57<b9~j4F*$H)tV<XvsoodBE)O`TZKL-eehyL?Wy= z0ysXHVhO&AIu+yY=%$fm#AF`r?oqc$<ddFhuz|Ql5Hh)vU)KyA;F|KXg(9uqK$0XR z*q!cKuMhN@KVFk<1UH(#|4{MkMNZcQh|%*$NIx`6t7@ull*)S5p(O$oKsdZ=Fpsr% tDEo0MM8Q4T!lq%~zo_iDGWl&nQMdG4pIo8E&Gx`zaLWc{=l}4(<G<0{XF&h} literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/1332500.thm.gif b/Database/CoolRunQuery/html/images/1332500.thm.gif new file mode 100644 index 0000000000000000000000000000000000000000..1ff7b67ad492abbfd299f05e7aa4d8c3514e0730 GIT binary patch literal 398 zcmV;90df9ENk%w1VTS;R0P+9;00000goK2I0RR60goK2~#>W5u{{R30A^8LZ0RaC1 zEC2ui0EYmF000C3Xu90~Fv>}*y*TU5yZ>M)j$~<`XsWJk>%MR-&vb3yc&_h!@BhG{ za7Zi~kI1BQ$!t2G(5Q4uty-_xtai)odcWYXcuX#v&*-#z&2GEj@VI<VuiNkVynfH` z`~Ow|f`fyA4gd-TiUkCSeuR*TjF5$Q00Wqr1CEOXjhdK_b(fx{oQsT&rkj=n1PTEL zl#m4q00N?Pq_C2xzP7lnuLiLex4Cn>#23fH0ngCU($LLz%EcAcqprNh!Pc(R;?ml> z+{@m`a@Wk?+XUnB0p-c(*c9~Z_U-C%r1InP`P-LHShsBM%)tXVO&}b01;GuwH^JeX ze+<bbtT&NUpFwm8`#sz=t0bX~`UVJC$+D$Omm(jUoGHkV&6_xL>fFh*r_Y~2g9;r= sw5ZXeNRujE%CxD|r%<Cxol3Q;)vH*uYTe4UtJkk!!-^eCHfaI?JA5Y3LI3~& literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/arrow-down.gif b/Database/CoolRunQuery/html/images/arrow-down.gif new file mode 100644 index 0000000000000000000000000000000000000000..83e836fc4c70966b7e79ae78d46d77e4acddce93 GIT binary patch literal 852 zcmZ?wbhEHb6ky<CXlDR{N(P3jk_w+vmB74FVl)IsPzWggWMO7tU}4Y!`5BZa7&r_W zI5}iIHY_;U%pokqb6~?l=VnG0k2xBJ2M%_KutvCaY+U5TBw=31qws0*$;ld=Y%C1c E0D#~Z$^ZZW literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/arrow-up.gif b/Database/CoolRunQuery/html/images/arrow-up.gif new file mode 100644 index 0000000000000000000000000000000000000000..a92773a116e556876408ffd1c942ef1265d37cd4 GIT binary patch literal 851 zcmZ?wbhEHb6ky<CXlDR{N(P3jk_w+vmB74FVl)IsPzWggWMO7tU}4Y!`5BZa7&r_V zI5}iIHY_;U%pt7AW8m=c00R??NeGAGq9ZM$x^X5m0w23IF*<e0yxg$l<YZ=MCI)K& DMlu&N literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/atlas1.ico b/Database/CoolRunQuery/html/images/atlas1.ico new file mode 100644 index 0000000000000000000000000000000000000000..57fa129e4be36c0e6d8022af160d87075aabbabe GIT binary patch literal 894 zcmb7DZAep57{1Zph*{AG{V9L+4N(w6DcPT>Fw!7UNDNH;A`Ay=$|Pw&hHZ1EwKZ@$ zGtw>0>1-|C(wfd~?(Wm+bZ$1MXwGfAd3Abs?45#`;*XxoIXve%&->haJ}!psgNH)F zpg)e~?8mU97>1pL1`>7(T2$Wv%<aVbKS1uOIgapWDO;LX#?2~z_q2qcUEWOP=xDWS zZj0LMTi9+#2>AlR&z<Tr=BJqR>5p<>RoAr@(HqVt<nTUrOO-f5tZesha<q;j2qoum zZzdJ2uC0kB0|#R>REA+l+ii|2P=HW;3WIVY@nK%|lS09<xC$muyr+L7KPi`WBsS&r zrL?QJi|*xD*<Dk6`5~T+piG+Zz~So`ucg*DNXCA9z`XlwK%4dYRa$F)$@@cR(qc~D zxp3ogAw$4zYIjf1Y_9-5Hy@~cD@n~_+)B#69QUA@UUMZeqqgx&b`dM{`OEmbX-10; zZ19>uFr@4=Cft8UEoZgMdtT7FwozwzjTCqE`WAu~$AktSPEM!((ip%ETURL%XXKXR zW}D9Dpm9EE_2x-WbaepxxXbJJ195w1Dqiz|5G�%BEIn*PziRR%nRjWe|g*CCku= z7Pqu@cEJ!<3LS&ZTM36DzouU57<b9~j4F*$H)tV<XvsoodBE)O`TZKL-eehyL?Wy= z0ysXHVhO&AIu+yY=%$fm#AF`r?oqc$<ddFhuz|Ql5Hh)vU)KyA;F|KXg(9uqK$0XR z*q!cKuMhN@KVFk<1UH(#|4{MkMNZcQh|%*$NIx`6t7@ull*)S5p(O$oKsdZ=Fpsr% tDEo0MM8Q4T!lq%~zo_iDGWl&nQMdG4pIo8E&Gx`zaLWc{=l}4(<G<0{XF&h} literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/book.gif b/Database/CoolRunQuery/html/images/book.gif new file mode 100644 index 0000000000000000000000000000000000000000..5b88ec6ca2bcd2a4f12c678e3cc0eba9a37d92ba GIT binary patch literal 361 zcmZ?wbhEHb6krfwxXQrr|NsBHckgbp-*j*7z55&P|N8Xn-kN(Gtv7D8+4%D0%T0Eh zHdt*~W3XnO>AKGkK5w+$cz@mfbtdc9o3H<P_v2=V&96?qdUg8MTEn&L&DQS;-*b2M z-J^L&j~5+3kaXZg*@@p@e*gLYXHUeQ54S%2|M&k;+M(x%p0Cng_3QJm!x@L)UVeMB z{N%C1V|Q2GeR<;L)#+FN{rR`vV*T!r-LKERet+ZrYQ5DjkH1`_zve{giKTK&Ki&Uy zefD(*RG|2i1uUQgB0+v)U~4<jP~f4%#du;#P-Tph)8tfR&j1rW$-p8m1s5^?mKMR; zjLTY1J1Pp;B$!;aY-pKrO{9zAV#L#D3_2F^{J9(wvV39<8e(!hLfq`UDtw|W4MrZE z=4L!H(*)!jnAtczOr?c2nbkdf_1UbJGpI7NMwfUhGwbQH?bzwfpdfYd(BYGAr(GNw FtO1!ys_Os% literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/cclose.jpg b/Database/CoolRunQuery/html/images/cclose.jpg new file mode 100644 index 0000000000000000000000000000000000000000..18bd1764ea28d23794357f5cf74e25802052a0b0 GIT binary patch literal 741 zcmex=<NpH&0WUXCHwH#VMut#^Fb0PIw;7xnIM~?O*;qN)+1WWcIk<R4czL+Fc_f8| z`9)-<<mF_gWMmXn^wbrUbd+UeG|V-13=B<7Oyt!qZ7qy!^o&i6K!z}Ka&q!;^GNXW zN*F21C>oIr{vTiv<X{kB;Adu3Vqg+vWEN!ne}qAvfq{_~=vt72p@5NznT3^&or9B$ z8>nEb00R>vGcywlGb<|#3s7|}P@aKBkX1<0(2-3zFp*uUP{gQl;zAB(r;P_igD!qh zF-|IK;^Yz&myncFRa4i{)G{$OGq<p`a&~cbbNBG{3JwVk3y+A5N=`{lOV7y6DlRE4 zE3c@mYHn$5YwzgnnlyRJ)M?Xa%$&7o@sg#>maka3YSZQ|TeofBv2)j<!$*!DJAUHi zsY{oyT)lSv#?4y~A3c8Z^x5+lFJFE9^!dxzZ{L6X`~~tCBLg$UTX3JD`AZP!FD4ci zW)^mkzZjXyftXp4g;mjzO~^5jJ+V+&$*7S-#A)KfjR!fEje|ajCKX-e5>qjGsQMA) zHL%Z!^H>vEK7)G<;jdc^Jj{&1$YT~{uxI%4pFuDyyvn}2##YP6xAjYZs*TFWJ-sIm z-+!w7ae0M!(4OnMTWzDBC0#gQt5UYHf45BF+-A8aZR)Q*AL*JskNlY5{z$<5MvhAL y&1JUcjax;kKOO%d@}4>KdX$RPvdM*cCwas#d1k9;DouDV^Q$RBe!&9%|2F|FHUygh literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/close.gif b/Database/CoolRunQuery/html/images/close.gif new file mode 100644 index 0000000000000000000000000000000000000000..791cf8bc366b14e0806e5c41d7c5de50cab66528 GIT binary patch literal 381 zcmZ?wbhEHblw*)%SZd7x1S~8pY;0^?TwFXnJbZk70s;a;LPBC<Vv>@Q^78Vks;X*g zYC1YP1_lP^=H?a_7Ot+Y9v&Xv-roNH{^8-_v9YlQ1qFqLg~i3iB_$;d4GqoB&F$^& zU0q#0Jv|d9Oqe!p+MGFa=FOY8Xwjm@ix)3nzI^@q^&2;C+_GiMjvYI8?%a9!@Zlpz zjvPC7?8J!^Cr_R{bLPysbLTExxNzyxrK?x3-n@D9*|TRaUcC7J`O*I$cmAUS#h)yU zTnzRMIv}5b{KUXE%VBPThmKVLi6uoRbFAiH&=6LeES7)t)G@1&xm~PW;TabM;u%=3 z?PBui5N1#~D$1a>^PxlC#y%-drcf)xA_l%N$u@Z>u{IWyK*qKtr?NH`f3r3&7ydTE z0GT327sWP(K=vYCXT2h3<3RHwS(h?_Fr_>fZid=09!?iICY@vk3ujZ#$`}n*7l~vi UHJ9cqH*ej(bNAl;AV&sk0N3%GO#lD@ literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/copen.jpg b/Database/CoolRunQuery/html/images/copen.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5d255c9e27675922f149169c1464c8aeb05759c1 GIT binary patch literal 749 zcmex=<NpH&0WUXCHwH#VMut#^Fb0PIw;7xnIM~?O*;qN)+1WWcIk<R4czL+Fc_f8| z`9)-<<mF_gWMmXn^wbrUbd+UeG|V-13=B<7Oyt!qZ7qy!^o&i6K!z}Ka&q!;^GNXW zN*F21C>oIr{vTiv<Y3@q5MX9hVqg+vWEN!ne}qAvfq{_~=vt72p@5NznT3^&or9B$ z8>nEb00R>vGcywlGb<|#3s7|}P@aKBkX1<0(2-3zFp*uUP{gQl;zAB(r;P_igD!qh zF-|IK;^Yz&myncFRa4i{)G{$OGq<p`a&~cbbNBG{3JwVk3y+A5N=`{lOV7y6DlRE4 zE3c@mYHn$5YwzgnnlyRJ)M?Xa%$&7o@sg#>maka3YSZQ|TeofBv2)j<!$*!DJAUHi zsY{oyT)lSv#?4y~A3c8Z^x5+lFJFE9^!dxzZ{L6X`~~tCBLg$UTX3JD`AZP!FD4ci zW)^mkzZjXyftXp4g;mjzO~^5jJ+V+&$*7S-#A)KfjR!fEje|ajCKX-e5>qjGsQMA) zHL%Z!^H>vEK7)G<;jdc^Jj{&1$YT~{uxEI)g;&bHW5U_>*`NJQdp27|zI>^y5_oJ$ z?-|CQGam2vip}=$Z_mwM^CI}H*sMI|w$J{R+{XfM=zU>-w?W<1JM8MZe}X^XO*~$^ z;Le##N7+72j@~R3>3EQ}sGh5?cV+CKh-ufJMLgPgAvZ1Z)(y+bJXyaG!vl<t&zUV3 I@c+LF0PjHx@Bjb+ literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/days.gif b/Database/CoolRunQuery/html/images/days.gif new file mode 100644 index 0000000000000000000000000000000000000000..eed854ffa69c719b6ea6aaf7d6cb563466b5f3e9 GIT binary patch literal 161 zcmV;S0ABw`Nk%w1VGsZi0J8u9|NsBp-QAg)nbE?s?z^hYGXULpRnI{Y)?72wP9pWz z%=qNp;Du(|X+-?-?f?J)A^8LV00000EC2ui01yBW000C|Fvv-(1UPGb1@Pb}0-a!< z=s*VJh6!rtX7KtsN5NnT@x?3;rT{=3d^MS((7+gwz!pLQc>ZjHMK0llVrQ=?YHW&P P2I#cf2HsfSln4MjonA%k literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/download.gif b/Database/CoolRunQuery/html/images/download.gif new file mode 100644 index 0000000000000000000000000000000000000000..73d8ff28e15a00d49364d8ec463630049e3c7eba GIT binary patch literal 120 zcmZ?wbhEHb6krfwSj52a|NsBHckinCW?a7gpsauX|1^f*i_$mmJE8cKg#iR~Km<rF z1GBBft~*=?UO$&<3WrpF?z{WNQBkzOb=ItDTJzSH%emy<Zz>aVe*b?byT5~u5c|Qq V`JGo7IgV*uoVn6!T@MR`H2_AxFbV(w literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/i_icon.png b/Database/CoolRunQuery/html/images/i_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8f830865c8a9f0d9535c9f175167b7a77f4a4144 GIT binary patch literal 4029 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 literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/info.gif b/Database/CoolRunQuery/html/images/info.gif new file mode 100644 index 0000000000000000000000000000000000000000..023f08f2cbbb671600123010365c384526effc31 GIT binary patch literal 212 zcmZ?wbhEHb6krfwIKseSJd@%7|NnRI-i=&i(09D3aED*eO7)v>&hNRqy7f?+%R<p* zmnY<J_l{d{*04X}>8E@1&$lnR*z37O`s=UHR&zOj{r!3S?WMc#uKF%h3|OwhfB_VL zvVg^OKqSac2G+m@3VkV>Y#FOosXy4aL3+!^&j#;xSPUCjI%Rm<3=fp-2|p|pA=BP= xg#Xi>y&7F!2?4&lI2xUKICryf#4Nn3>gdXKH$1JV;du4q%US(KKRp#0tN~cMR|o(A literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/minus.gif b/Database/CoolRunQuery/html/images/minus.gif new file mode 100644 index 0000000000000000000000000000000000000000..9b3604b27b91a8065d91eabd0746e7e0e48d443c GIT binary patch literal 974 zcmYLIe`phD7=DiCOvTp5BgvwxF6oArQrcQmmzkQhB_wVQ>L1#%8Adb8kfEa+Wewx% zvTM<86pa#!NU)&<8C{SenaVgq$!;0A!%eyEk1h<Ums=U38`)7A1iyWE6ZZY_e(#Ux zeR$sY<BPl+_Pr8sMJt{F_C1613wil|C4Wn*{h^26N{c1A@>?ypEOsA^>YEL*R4IHT zE5E3<x<2($I(@z19f`Gfg<>D%q<f00>6v9ox<@gJvRa$JCgyL+wGG;(RMvE{OuHI` z;fefBseNx~{#rr4uhus7=sTIl(}q~C6pFI4s`8&_($__AIF`F9<!{SUS+REvs%y3G zgC`oB4ZW_v)E}MrFqd1F#G+DJtw|+CUDNe?BTWe(syZcnHzSoRVo|QFs&g0fnI&;B z94p+G1q69aW5;`7wm8rXVEj@`6GLy~e6@~Q20KkZfmj@x1DqM<06#E8{d4O-fGGgV z3gxkzMbnTz8|eW#0J0y%-AYD5MyLbyHi6ncpa&Re!T^kL6c{nu945W;lw0sGBACQ9 zZ)065oE&!3YHtkNH3%Gd;MZX*>xU2oKaHVbRQ)&>1eK5g;;=C${tRWV;y=kz1&<e= zsrPn1%db!&v)JqcCxDW=;M%}q3P~RPBnS&~8H5KR;0P88k_7x&w6F{=8*se#dkgJN z3gBrFO{2Qu;LHI^1YHCIg%eW`98^6$t`m$?!)_B(+>XGH5JoA{>%_ZBv?Sqp0^jjd zRc(#qj#7By$?14NbC?6q-z^6I49pxggfg~A*s0-|!83+>l_4Am@G%orEL9Dr1}mM0 zal+igl(V=c7_bV4&>`mkeV&1$Vu7P?0X*)<-vjvh5T^GdF>o$`lQW3WyyqW_QAW36 zj6Y#*{t~^9IwzO1!ya~S{OV*peBNOln>Y=1=t46)mhFpw8;Lw`u^&&lb90|Hb+*~x zcrgAwcI~tHzQ|&(?S=M!?0Emh8*NPJZP)tB3R2G<?#+%f$#Uzi{yhob75Cm|rt6T) zeI>-E+7Gj~tMR1~w(USqZYbqFd*MzfzMg#(N7%2oIj4U5#Pi*@t-gyb$-ic$OC`qb V8m@OppITn-h}`gag!4)G{{`}CShD~C literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/newtopic.gif b/Database/CoolRunQuery/html/images/newtopic.gif new file mode 100644 index 0000000000000000000000000000000000000000..aae4c27f1d3e9a5a36fc1e9fe45e84990890d2e2 GIT binary patch literal 340 zcmZ?wbhEHb6krfwxXQrr|NsBHckeRHWT>l4`2QcsJ@oYHhKCQ=Uc9*I@81Jw&o21< zY3H|ZdoEsB^y$;ib7vR)`nmty*@aiHEWLVV$<@nCK7ZcX+M0Fc^3pkT+8#bwd*#Z~ z@89;ccjWx~wQtp`={t9>c=&M5)@@6cEbX5#p=|1u>Yu;%F;EpK{$v50rvoBEeqvxN zJkU_!p~J;^Vo6Y?43Ca-&vWG!YO`C{ggFSrFgG|R%~?@&wjpgLb7KMLhde2p8wT&5 zt=sxCBIFl$dVC^VYg>kguOzzydmp=^Pl>RQxUiInkkX_?UXJ<mc~#~x3-WVu@e6Xv TEb&{vevbF%PPc8&jttfS?8SjX literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/open.gif b/Database/CoolRunQuery/html/images/open.gif new file mode 100644 index 0000000000000000000000000000000000000000..36f3cc0d3bd3da90cb063e75b1b650e4409e0f5c GIT binary patch literal 389 zcmZ?wbhEHblw*)%xXJ(m|NsBz<KxlLP)$mV3JmZH3iQ5r>-g>+E4Oc1=I-YB|Hqx* zU+?_-a{K4!Ti-w4{Py9-k59MWzq<1F#pTz}F1~tt;qkpwS1;~2(AU0nZtsONyH6e4 zcJ}1XMe`@;=ceZ6q?nr-H8qsm+F1Pie&^eVn_u7G`10=hhgVk~-#h)__K90p4?lfy zcKha~#}9AWwQa?$={*yAo4Y&eR8<spv^CnAt74);#6*RqB*hgK<Rm2}fldWG89^xi zWMO1ruwu{w34#2?z_!R?Zh?o6RR6BlqLVpR^DnF@@md?T{>GZJ({HQxpAucFB^epW zbok&Ko3mOsCVD6w$dNd8#kb+cB^Cw-CNn)#CX-m1l12tg{U||SRkH>Lb6@^YPF`QG zl8N5>3qq8nd<`VMRhV@n%vl2rnG7P;n9K!y1TB~pqIF8Nykr}ga-!r)<g1&6<1Bib fbmIA@GO;9Zi#HhE^ipJ+bB|l>@w4YqjttfSsQIZ@ literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/plus.gif b/Database/CoolRunQuery/html/images/plus.gif new file mode 100644 index 0000000000000000000000000000000000000000..a706eb249b9a650b7d017bb68f1a7060d51fc0fc GIT binary patch literal 981 zcmX9-e`p(J7=Et#(R8yV9!Z-*8L5b1s4mkuTMV_+Eb~X%I@p-FAdMSEMwKd~E#v5} zmx{H9kwGYR20J7m)=;zrDdUJ;J;IpBR@dt&ZO~HF<QTUZ=8-DIeSUZQ=Xl@mectDJ zzUSkPyczE7iZ-GVyMSw7Fg~4;Zxu2Nl6FrIy_Xc1mGqog_+9Hb*n9p$Qn_2qUX%4| zE%|j;%okI0(%^7hx~T+Si?sPd+C#lq(&W2Z_L>sEBFeXlV!pslr84tUFnqS{nNa*n zHnSl24y4laVqsgaJ)%5fdR~frk`9C;`omgbL(AqA`A%`-QpWXEP<x<Dd8N2b)lVoJ z#cWQlRcq-vNxE5h`h{NIkkge7P0AN0rcy~t{*&B$j>?ItZ^X01>0me_<`m^l(JkNK zQ)Ptzz+D7#2_zY}0*s)4CS&=mj5U_lKYX+qv;W-cFb1Os)}#BE-7j)D2n6oK%K!-g zV_Pczu;5th5^Xm9u<{^>K|s<VB@mXDATxxl5uy+>LZJF!nMA88=KTs-1k9QT>~K3M z3lxT(#w@_2j$3E2gH;wS^nAaDrV<1e9F;m;Z(D<)fEz)NiXFp(u{-KT6f2C0ALI>A zn4B&JJJVnd2m%$L1llYC|HQEC11o@%df<70*#vkF+z^Nj<T3~cLckKt67U4vhuCXl zaM^(4t@7Tg54-@5Hm(TD6*p@tEED($1PUjkpnl*sb%8c!RYi-5$u*(-HiWk+(J4}^ zk}^+Nj)vtqT~@bQ_9)#L-StPiRrliyFXr%9H;x)cdFQuktEwDhM-1A(!5A3>7oTvY zP*!1j*aWQ1DnSb!FwHr<+6}=pO5Y8uF!M2!pN8LHnHfT<{CE|A3S;+fpG>SBq1m?o zYc<B`Fh0gVQSVs5$IfS+p^57;Tc5vYJSNrc3pQu2{#G|W-eCJ6xpMq^_;b78vA%Su zebVj;3_G^GJ>f6hmVrqpUJo}guqNMG*$y>5`P}&}r~myt)9})TN78Y@!dtsovCn(v z4dF|lb*r;qL9F@Zu{~b?YAD`fKcTM=^?w)lbn+ojzjuQ9-uAAx^kejwPa8Y@gNuj8 hZzPzev6`b|38DV^sZ^!HoV)aE*FfJ)qTP#E{s$QEd*J{8 literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/printer.png b/Database/CoolRunQuery/html/images/printer.png new file mode 100644 index 0000000000000000000000000000000000000000..df629221c2c4668d207a072957dff956a34dce63 GIT binary patch literal 13761 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 Ptsv6k@?sUDhJpVB+P<}H literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/printer2.png b/Database/CoolRunQuery/html/images/printer2.png new file mode 100644 index 0000000000000000000000000000000000000000..b788358b399e75cf742ff9103366cd8975960c9b GIT binary patch literal 38049 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� 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 W4C6g&EWFDA0000<MNUMnLSTX+0bxM^ literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/printtopic.gif b/Database/CoolRunQuery/html/images/printtopic.gif new file mode 100644 index 0000000000000000000000000000000000000000..63565cfc7ef04a0031bbe2c535c188c5aa80149d GIT binary patch literal 221 zcmZ?wbhEHb6krfwIKsg2|NsBHckjl;#4MfHS6No@|NqS|pD!$))B61Ju_g1`e*L=o z_wV)Jzpg%ia`e})YtNq?`|<7a_ivZKe7e9u98mnp0#>L4B0+XCutp^)^rdJrWvp8D z{M8~|gEL>|9CKdy{;=$lX4j+{5nmGQuQ)7B(2(g-W?;9B;1_(^=^!DoZ_@h)>%W{V mcS?T6u+6#uq1@U}{0wU|JJZD*{5LF2P3-jb)!J1Q8LR=98Cci= literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/question-1.gif b/Database/CoolRunQuery/html/images/question-1.gif new file mode 100644 index 0000000000000000000000000000000000000000..9b1cfe990baed8d24c5fa1bcb0d7b5b145541a16 GIT binary patch literal 214 zcmZ?wbhEHb6krfwIKsg2KaJu4|NnRI-u>U>@c--k{|gfS7YO{{RR6!(;QyZf|2Oyl zpBC`{==}dH3;w^k|NqhX|CI_1qyWXAEMVn2AQEIJ18eXCg}xL`wv1J))E_V%GuV@N zVV9?u$a-CtbxsW%A_SO>8~8aq-o&&YSXj2$QO4s{KbwP$<X){eHx}q-HXmrNt~)A_ YQy3+9f8VoGHS_(i7W*`)C^A?B0A}S)r~m)} literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/question.gif b/Database/CoolRunQuery/html/images/question.gif new file mode 100644 index 0000000000000000000000000000000000000000..9b1cfe990baed8d24c5fa1bcb0d7b5b145541a16 GIT binary patch literal 214 zcmZ?wbhEHb6krfwIKsg2KaJu4|NnRI-u>U>@c--k{|gfS7YO{{RR6!(;QyZf|2Oyl zpBC`{==}dH3;w^k|NqhX|CI_1qyWXAEMVn2AQEIJ18eXCg}xL`wv1J))E_V%GuV@N zVV9?u$a-CtbxsW%A_SO>8~8aq-o&&YSXj2$QO4s{KbwP$<X){eHx}q-HXmrNt~)A_ YQy3+9f8VoGHS_(i7W*`)C^A?B0A}S)r~m)} literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/recentchanges.gif b/Database/CoolRunQuery/html/images/recentchanges.gif new file mode 100644 index 0000000000000000000000000000000000000000..d84d7f8f096a549ab867107e0788a354a2ce6852 GIT binary patch literal 220 zcmZ?wbhEHb6krfwIKsei_wL>Q|NnpcR<vf#6dxa-8#fx>ym|iN!<(Hu`)=Q^J9VmU z@7|ujfB&poH~HAHZHEtUI(~fn(W6_B9NEl398mnp0#>L4B0+XCutq8<^rdLBWvp8D z{K38r(p#Kz?;qk}X%_guMed#07FDrc*F|9>8gguH4;KoEa0`hrvTBM5vg^KT&@hvT kVrsnjqb>AFcFNUUEt$54gMqdSTbSILy?oT#loc7Q0ZC?1P5=M^ literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/rootdrawing-logo.png b/Database/CoolRunQuery/html/images/rootdrawing-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..20ac823f6eec9b3e894eeea35dc5fb2fd30fa13c GIT binary patch literal 15051 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 literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/rootlogo.gif b/Database/CoolRunQuery/html/images/rootlogo.gif new file mode 100644 index 0000000000000000000000000000000000000000..75de21ab2ceceb3edd9f758e72c23105c5f7b400 GIT binary patch literal 19590 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 a2$2v5p>7JT@Cvao3$<_yxe#0e0suP*A5#SY literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/statistics.gif b/Database/CoolRunQuery/html/images/statistics.gif new file mode 100644 index 0000000000000000000000000000000000000000..c03436a4aaa95eb003c6c490bee28e39bfebd976 GIT binary patch literal 118 zcmZ?wbhEHb6krfwSj52a|NsBHcke!U@L=Z5nNnH@7+^s0CkvRV10q3c8JMjlcHQAJ z@cOx|HUGs~(-ll2OpiIbG&)&ddUU*Zo^s&NKKbawul9Vhh~j%1Ea~r-xa_b@oX%AB LqmHpE3=Gx)O@=A* literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/tiny_red.gif b/Database/CoolRunQuery/html/images/tiny_red.gif new file mode 100644 index 0000000000000000000000000000000000000000..dba81f5113f127557dfdfad9ad5208bfd694c322 GIT binary patch literal 310 zcmZ?wbhEHb<YM4rn8?g<=FFM@X=(o%82<nNulS$a&ow02*)hP?NY8+ok%56h@h1x_ z0|PUI4v-Dhz{SA8BtA#Ih^;}2saSoTbz%0bM~53l+^cQ|BB=#w<N#`7VB(r1Y^K!A z%n&Rff@%~S0~=79XAZl8;EI|>f=ZVnkn}S!FamXRfb`2GSO_27H%%Z}3|T)5P&Y_F z7fW06NtuTW1agq185!7NwzIS;CkZcfsalC-I12+zHxJMwD|QRAF)({oae<8I1pAa7 SVf&P{#ZrcQ86=8C7_0#!o;rsB literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/tree_icon.gif b/Database/CoolRunQuery/html/images/tree_icon.gif new file mode 100644 index 0000000000000000000000000000000000000000..8ae8d18f1debe966e61da8820a646ba5676ce5f7 GIT binary patch literal 1891 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 OEbk!=*T&;;2=D*iWTfl> literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/viewtopic.gif b/Database/CoolRunQuery/html/images/viewtopic.gif new file mode 100644 index 0000000000000000000000000000000000000000..217afe28b9fcd1032e29d6751d974f0c25163c26 GIT binary patch literal 77 zcmZ?wbh9u|6krfwIK;s49|UgRy?b}dmYIERMcH6JkkDafU}E56;9+23ma^Do^Wnov b3H6;4K3cNpq|<9#o)xIgTI4&Mfx#L8r9dLc literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/wip.gif b/Database/CoolRunQuery/html/images/wip.gif new file mode 100644 index 0000000000000000000000000000000000000000..d1f3b7095d09d9a0c22c8448ad3e6c7eb73f865d GIT binary patch literal 594 zcmZ?wbhEHb6krfwcoxKP_wL>Q|NsB__phxZ|Nno-zyJRI`tz@$HKVaL^TTW9_D-On z0|NuY|NmD1|69zOCja4$^8f!XKfkKJcqsq>uUT7X{)<P7|NlEp>X&2ZVAMC_3=I=} z@zgLlRPf^)<(adJm^c{j-Ztpy&X0&-N=^}NZIP+0%}GiWX={~<ix>I-&#a{lXo9?> z8{fZgCb_w6Gp7Shv-b5DxOze7$46ydBhFu6Ro*;TeEq~=Qon3>x9s(s23OAMe0{In zH=&@rw_x=O<-I%9Hm?``|KD!vw8Cjq_}F+D*|`{>-ZPjqrEuOH#ZPaPGSWm_TctmL zP&s;3Yu7flJ6C0|z5WXH)L;OLKUo+V7=jpdK#l>$2?P8328O2QmewXVrp``w_O4F0 zCRWDDjLbqp%u^Uyo0z6>iHV4aaZO>GJK593*-e4hTy*kshI!hqiu|U!hAcW8H!~>( z2Kcx;i1YHX2s3ZykmHu+@s+SPlJsB^;ArCD6x85Rk>T@F*W$iCm)Xx!Q_6-#Pn`SA z0Y-UcD`{g@9!GDlyW9y*Cxm7A6ZZU2<i4sRZ_DAb!SP6liZ8Q}$_bZFVFs5z2}>b{ NlM_xlHZm|+0{}LTw4VR~ literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/xml-feed.gif b/Database/CoolRunQuery/html/images/xml-feed.gif new file mode 100644 index 0000000000000000000000000000000000000000..ecb09575418861d80c63d143660290732b84678e GIT binary patch literal 429 zcmZ?wbhEHbRAJy_xXQqAHG<{il>-lBS^j_f@V`j(!Sbf>FCRROWBHQ8`u60;56P^* zzP!Ibujv2k;_nYGTqrVrzIWmO#~0^2GXH;e^=XUq|5lU#w+@~QWjUL!{QvF4=bI<K zSlx6bQRG98$dhutyLl=f4lew0{owx^m1ot)pARkkH7)qVx+X<F#-E#-{$Jemzu)Em zmZn-`rvJ0T4+XH;OELaGvE)Axq%jZ)6o0aSRqB99ke?XXP95OP@z9a#Ke2eZYPQw< z3oA@cCHbw7She+L2QP<FsE$*_(WI$rZ)Ex+mxX3%OjAfn>hHE>a}+pdsF%dZ5LD=| z#BIoD$fL)R)+MjaoZe*E(iZKfn$D=lZj>fa!mKi_xiu;<$&gLPH;q*xU1G(IRRQgO zEX>S3QX36fl}q%t&s^=V*~_0Uvgj}qhaSUrHfe2bE&ts{vGdtA*2!*S^4s2EsK(`Y r&xrl1FO#00(U<3bUyZoF{`+ZkFICdR^-y;+hp<-6iGqin3Jlf&%kitR literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/images/xml-small.gif b/Database/CoolRunQuery/html/images/xml-small.gif new file mode 100644 index 0000000000000000000000000000000000000000..2534c16fe51f0d3582ece04cbb8e2dc2e5a967e3 GIT binary patch literal 121 zcmZ?wbhEHb<Y(Yy*v!CCYs@s?k@;LG%Y`E2FDb15YgGQXn*5p;{C{=v=R*ts|NqZ` z3UokRkX{BBJ%PEN+Olmg&X#Mm2=y&mQR`BqvM{LXrpGCl2`k@ZUz_Qtr4yZAA;i*m LLPbb`k--`Oinu6d literal 0 HcmV?d00001 diff --git a/Database/CoolRunQuery/html/js/animatedcollapse.js b/Database/CoolRunQuery/html/js/animatedcollapse.js new file mode 100644 index 00000000000..2e83e4d6371 --- /dev/null +++ b/Database/CoolRunQuery/html/js/animatedcollapse.js @@ -0,0 +1,223 @@ +//** Animated Collapsible DIV v2.0- (c) Dynamic Drive DHTML code library: http://www.dynamicdrive.com. +//** May 24th, 08'- Script rewritten and updated to 2.0. +//** June 4th, 08'- Version 2.01: Bug fix to work with jquery 1.2.6 (which changed the way attr() behaves). +//** March 5th, 09'- Version 2.2, which adds the following: + //1) ontoggle($, divobj, state) event that fires each time a DIV is expanded/collapsed, including when the page 1st loads + //2) Ability to expand a DIV via a URL parameter string, ie: index.htm?expanddiv=jason or index.htm?expanddiv=jason,kelly + +//** March 9th, 09'- Version 2.2.1: Optimized ontoggle event handler slightly. +//** July 3rd, 09'- Version 2.4, which adds the following: + //1) You can now insert rel="expand[divid] | collapse[divid] | toggle[divid]" inside arbitrary links to act as DIV togglers + //2) For image toggler links, you can insert the attributes "data-openimage" and "data-closedimage" to update its image based on the DIV state + +var animatedcollapse={ +divholders: {}, //structure: {div.id, div.attrs, div.$divref, div.$togglerimage} +divgroups: {}, //structure: {groupname.count, groupname.lastactivedivid} +lastactiveingroup: {}, //structure: {lastactivediv.id} +preloadimages: [], + +show:function(divids){ //public method + if (typeof divids=="object"){ + for (var i=0; i<divids.length; i++) + this.showhide(divids[i], "show") + } + else + this.showhide(divids, "show") +}, + +hide:function(divids){ //public method + if (typeof divids=="object"){ + for (var i=0; i<divids.length; i++) + this.showhide(divids[i], "hide") + } + else + this.showhide(divids, "hide") +}, + +toggle:function(divid){ //public method + if (typeof divid=="object") + divid=divid[0] + this.showhide(divid, "toggle") +}, + +addDiv:function(divid, attrstring){ //public function + this.divholders[divid]=({id: divid, $divref: null, attrs: attrstring}) + this.divholders[divid].getAttr=function(name){ //assign getAttr() function to each divholder object + var attr=new RegExp(name+"=([^,]+)", "i") //get name/value config pair (ie: width=400px,) + return (attr.test(this.attrs) && parseInt(RegExp.$1)!=0)? RegExp.$1 : null //return value portion (string), or 0 (false) if none found + } + this.currentid=divid //keep track of current div object being manipulated (in the event of chaining) + return this +}, + +showhide:function(divid, action){ + var $divref=this.divholders[divid].$divref //reference collapsible DIV + if (this.divholders[divid] && $divref.length==1){ //if DIV exists + var targetgroup=this.divgroups[$divref.attr('groupname')] //find out which group DIV belongs to (if any) + if ($divref.attr('groupname') && targetgroup.count>1 && (action=="show" || action=="toggle" && $divref.css('display')=='none')){ //If current DIV belongs to a group + if (targetgroup.lastactivedivid && targetgroup.lastactivedivid!=divid) //if last active DIV is set + this.slideengine(targetgroup.lastactivedivid, 'hide') //hide last active DIV within group first + this.slideengine(divid, 'show') + targetgroup.lastactivedivid=divid //remember last active DIV + } + else{ + this.slideengine(divid, action) + } + } +}, + +slideengine:function(divid, action){ + var $divref=this.divholders[divid].$divref + var $togglerimage=this.divholders[divid].$togglerimage + if (this.divholders[divid] && $divref.length==1){ //if this DIV exists + var animateSetting={height: action} + if ($divref.attr('fade')) + animateSetting.opacity=action + $divref.animate(animateSetting, $divref.attr('speed')? parseInt($divref.attr('speed')) : 500, function(){ + if ($togglerimage){ + $togglerimage.attr('src', ($divref.css('display')=="none")? $togglerimage.data('srcs').closed : $togglerimage.data('srcs').open) + } + if (animatedcollapse.ontoggle){ + try{ + animatedcollapse.ontoggle(jQuery, $divref.get(0), $divref.css('display')) + } + catch(e){ + alert("An error exists inside your \"ontoggle\" function:\n\n"+e+"\n\nAborting execution of function.") + } + } + }) + return false + } +}, + +generatemap:function(){ + var map={} + for (var i=0; i<arguments.length; i++){ + if (arguments[i][1]!=null){ //do not generate name/value pair if value is null + map[arguments[i][0]]=arguments[i][1] + } + } + return map +}, + +init:function(){ + var ac=this + jQuery(document).ready(function($){ + animatedcollapse.ontoggle=animatedcollapse.ontoggle || null + var urlparamopenids=animatedcollapse.urlparamselect() //Get div ids that should be expanded based on the url (['div1','div2',etc]) + var persistopenids=ac.getCookie('acopendivids') //Get list of div ids that should be expanded due to persistence ('div1,div2,etc') + var groupswithpersist=ac.getCookie('acgroupswithpersist') //Get list of group names that have 1 or more divs with "persist" attribute defined + if (persistopenids!=null) //if cookie isn't null (is null if first time page loads, and cookie hasnt been set yet) + persistopenids=(persistopenids=='nada')? [] : persistopenids.split(',') //if no divs are persisted, set to empty array, else, array of div ids + groupswithpersist=(groupswithpersist==null || groupswithpersist=='nada')? [] : groupswithpersist.split(',') //Get list of groups with divs that are persisted + jQuery.each(ac.divholders, function(){ //loop through each collapsible DIV object + this.$divref=$('#'+this.id) + if ((this.getAttr('persist') || jQuery.inArray(this.getAttr('group'), groupswithpersist)!=-1) && persistopenids!=null){ //if this div carries a user "persist" setting, or belong to a group with at least one div that does + var cssdisplay=(jQuery.inArray(this.id, persistopenids)!=-1)? 'block' : 'none' + } + else{ + var cssdisplay=this.getAttr('hide')? 'none' : null + } + if (urlparamopenids[0]=="all" || jQuery.inArray(this.id, urlparamopenids)!=-1){ //if url parameter string contains the single array element "all", or this div's ID + cssdisplay='block' //set div to "block", overriding any other setting + } + else if (urlparamopenids[0]=="none"){ + cssdisplay='none' //set div to "none", overriding any other setting + } + this.$divref.css(ac.generatemap(['height', this.getAttr('height')], ['display', cssdisplay])) + this.$divref.attr(ac.generatemap(['groupname', this.getAttr('group')], ['fade', this.getAttr('fade')], ['speed', this.getAttr('speed')])) + if (this.getAttr('group')){ //if this DIV has the "group" attr defined + var targetgroup=ac.divgroups[this.getAttr('group')] || (ac.divgroups[this.getAttr('group')]={}) //Get settings for this group, or if it no settings exist yet, create blank object to store them in + targetgroup.count=(targetgroup.count||0)+1 //count # of DIVs within this group + if (jQuery.inArray(this.id, urlparamopenids)!=-1){ //if url parameter string contains this div's ID + targetgroup.lastactivedivid=this.id //remember this DIV as the last "active" DIV (this DIV will be expanded). Overrides other settings + targetgroup.overridepersist=1 //Indicate to override persisted div that would have been expanded + } + if (!targetgroup.lastactivedivid && this.$divref.css('display')!='none' || cssdisplay=="block" && typeof targetgroup.overridepersist=="undefined") //if this DIV was open by default or should be open due to persistence + targetgroup.lastactivedivid=this.id //remember this DIV as the last "active" DIV (this DIV will be expanded) + this.$divref.css({display:'none'}) //hide any DIV that's part of said group for now + } + }) //end divholders.each + jQuery.each(ac.divgroups, function(){ //loop through each group + if (this.lastactivedivid && urlparamopenids[0]!="none") //show last "active" DIV within each group (one that should be expanded), unless url param="none" + ac.divholders[this.lastactivedivid].$divref.show() + }) + if (animatedcollapse.ontoggle){ + jQuery.each(ac.divholders, function(){ //loop through each collapsible DIV object and fire ontoggle event + animatedcollapse.ontoggle(jQuery, this.$divref.get(0), this.$divref.css('display')) + }) + } + //Parse page for links containing rel attribute + var $allcontrols=$('a[rel]').filter('[rel^="collapse["], [rel^="expand["], [rel^="toggle["]') //get all elements on page with rel="collapse[]", "expand[]" and "toggle[]" + $allcontrols.each(function(){ //loop though each control link + this._divids=this.getAttribute('rel').replace(/(^\w+)|(\s+)/g, "").replace(/[\[\]']/g, "") //cache value 'div1,div2,etc' within identifier[div1,div2,etc] + if (this.getElementsByTagName('img').length==1 && ac.divholders[this._divids]){ //if control is an image link that toggles a single DIV (must be one to one to update status image) + animatedcollapse.preloadimage(this.getAttribute('data-openimage'), this.getAttribute('data-closedimage')) //preload control images (if defined) + $togglerimage=$(this).find('img').eq(0).data('srcs', {open:this.getAttribute('data-openimage'), closed:this.getAttribute('data-closedimage')}) //remember open and closed images' paths + ac.divholders[this._divids].$togglerimage=$(this).find('img').eq(0) //save reference to toggler image (to be updated inside slideengine() + ac.divholders[this._divids].$togglerimage.attr('src', (ac.divholders[this._divids].$divref.css('display')=="none")? $togglerimage.data('srcs').closed : $togglerimage.data('srcs').open) + } + $(this).click(function(){ //assign click behavior to each control link + var relattr=this.getAttribute('rel') + var divids=(this._divids=="")? [] : this._divids.split(',') //convert 'div1,div2,etc' to array + if (divids.length>0){ + animatedcollapse[/expand/i.test(relattr)? 'show' : /collapse/i.test(relattr)? 'hide' : 'toggle'](divids) //call corresponding public function + return false + } + }) //end control.click + })// end control.each + + $(window).bind('unload', function(){ + ac.uninit() + }) + }) //end doc.ready() +}, + +uninit:function(){ + var opendivids='', groupswithpersist='' + jQuery.each(this.divholders, function(){ + if (this.$divref.css('display')!='none'){ + opendivids+=this.id+',' //store ids of DIVs that are expanded when page unloads: 'div1,div2,etc' + } + if (this.getAttr('group') && this.getAttr('persist')) + groupswithpersist+=this.getAttr('group')+',' //store groups with which at least one DIV has persistance enabled: 'group1,group2,etc' + }) + opendivids=(opendivids=='')? 'nada' : opendivids.replace(/,$/, '') + groupswithpersist=(groupswithpersist=='')? 'nada' : groupswithpersist.replace(/,$/, '') + this.setCookie('acopendivids', opendivids) + this.setCookie('acgroupswithpersist', groupswithpersist) +}, + +getCookie:function(Name){ + var re=new RegExp(Name+"=[^;]*", "i"); //construct RE to search for target name/value pair + if (document.cookie.match(re)) //if cookie found + return document.cookie.match(re)[0].split("=")[1] //return its value + return null +}, + +setCookie:function(name, value, days){ + if (typeof days!="undefined"){ //if set persistent cookie + var expireDate = new Date() + expireDate.setDate(expireDate.getDate()+days) + document.cookie = name+"="+value+"; path=/; expires="+expireDate.toGMTString() + } + else //else if this is a session only cookie + document.cookie = name+"="+value+"; path=/" +}, + +urlparamselect:function(){ + window.location.search.match(/expanddiv=([\w\-_,]+)/i) //search for expanddiv=divid or divid1,divid2,etc + return (RegExp.$1!="")? RegExp.$1.split(",") : [] +}, + +preloadimage:function(){ + var preloadimages=this.preloadimages + for (var i=0; i<arguments.length; i++){ + if (arguments[i] && arguments[i].length>0){ + preloadimages[preloadimages.length]=new Image() + preloadimages[preloadimages.length-1].src=arguments[i] + } + } +} + +} \ No newline at end of file diff --git a/Database/CoolRunQuery/html/js/dw_event.js b/Database/CoolRunQuery/html/js/dw_event.js new file mode 100755 index 00000000000..f1ad9dfee06 --- /dev/null +++ b/Database/CoolRunQuery/html/js/dw_event.js @@ -0,0 +1,41 @@ +// dw_event.js version date Apr 2008 +// basic event handling file from dyn-web.com + +var dw_Event = { + + add: function(obj, etype, fp, cap) { + cap = cap || false; + if (obj.addEventListener) obj.addEventListener(etype, fp, cap); + else if (obj.attachEvent) obj.attachEvent("on" + etype, fp); + }, + + remove: function(obj, etype, fp, cap) { + cap = cap || false; + if (obj.removeEventListener) obj.removeEventListener(etype, fp, cap); + else if (obj.detachEvent) obj.detachEvent("on" + etype, fp); + }, + + DOMit: function(e) { + e = e? e: window.event; // e IS passed when using attachEvent though ... + if (!e.target) e.target = e.srcElement; + if (!e.preventDefault) e.preventDefault = function () { e.returnValue = false; return false; } + if (!e.stopPropagation) e.stopPropagation = function () { e.cancelBubble = true; } + return e; + }, + + getTarget: function(e) { + e = dw_Event.DOMit(e); var tgt = e.target; + if (tgt.nodeType != 1) tgt = tgt.parentNode; // safari... + return tgt; + } + +} + +// Danny Goodman's version (DHTML def ref) +function addLoadEvent(func) { + var oldQueue = window.onload? window.onload: function() {}; + window.onload = function() { + oldQueue(); + func(); + } +} diff --git a/Database/CoolRunQuery/html/js/dw_tooltip.js b/Database/CoolRunQuery/html/js/dw_tooltip.js new file mode 100755 index 00000000000..da22a5fbd78 --- /dev/null +++ b/Database/CoolRunQuery/html/js/dw_tooltip.js @@ -0,0 +1,502 @@ +/************************************************************************* + This code is from Dynamic Web Coding at dyn-web.com + Copyright 2003-2008 by Sharon Paine + See Terms of Use at www.dyn-web.com/business/terms.php + regarding conditions under which you may use this code. + This notice must be retained in the code as is! + + version date: Aug 2008 + requires: dw_event.js (april 2008 version) + and dw_viewport.js (march 2008 version) +*************************************************************************/ + +var dw_Tooltip = { + offX: 12, + offY: 12, + showDelay: 100, + hideDelay: 100, + hoverDelay: 500, // for hover tip + tipID: "tipDiv", + actuatorClass: "showTip", + maxLoops: 2, // for actuator check (linked image, etc.) + activateOnfocus: true, + tip: null, shim:null, timer: 0, hoverTimer: 0, + active: false, actuator: null, resetFlag: false, restored: true, + on_show: function() {}, on_position: function() {}, on_hide: function() {}, + + init: function() { + var _this = dw_Tooltip; + if ( document.createElement && document.body && typeof document.body.appendChild != "undefined" ) { + var el = document.createElement("div"); + el.id = _this.tipID; el.style.position = 'absolute'; + el.style.visibility = 'hidden'; el.style.zIndex = 10000; + document.body.appendChild(el); + _this.tip = document.getElementById( _this.tipID); + _this.setDefaults(); + if ( _this.checkOverlaySupport() ) { _this.prepOverlay(); } + _this.setPosition(0, 0); + } + }, + + setDefaults: function() { // called when props changed (resetFlag set) + if ( !this.defaultProps ) this.defaultProps = {}; + // prop name, type, default + var list = [ ['followMouse', 'boolean', true], ['sticky', 'boolean', false], ['klass', 'string', ''], + ['hoverable', 'boolean', false], ['duration', 'number', 0], + ['jumpAbove', 'boolean', true], ['jumpLeft', 'boolean', true], + ['Left', 'boolean', false], ['Above', 'boolean', false], + ['positionFn', 'function', this.positionRelEvent], + ['wrapFn', 'function', function(str) { return str; } ] ]; + + for (var i=0; list[i]; i++) { + this[ list[i][0] ] = ( typeof this.defaultProps[ list[i][0] ] == list[i][1] )? + this.defaultProps[ list[i][0] ]: list[i][2]; + } + + this.tip.className = this.klass; + this.coordinateOptions(); + }, + + activate: function(e, tgt, msg, id) { + var _this = dw_Tooltip; if (!_this.tip) return; + _this.clearTimer('timer'); _this.clearTimer('hoverTimer'); + if ( !_this.restored ) _this.handleRestore(); + _this.actuator = tgt; dw_Viewport.getAll(); + _this.getContent(e, tgt, msg, id); _this.restored = false; + if ( !_this.tip.innerHTML ) return; _this.active = true; + _this.handleOptions(e); _this.positionFn(e, tgt); _this.adjust(); + _this.timer = setTimeout(_this.show, _this.showDelay); + }, + + getContent: function(e, tgt, msg, id) { + msg = msg || ''; + if (id && !msg) { + var obj = (id && this.content_vars && this.content_vars[id])? this.content_vars[id]: false; + if ( typeof obj == 'string' ) { + msg = obj; + } else if ( typeof obj == 'object' ) { + this.checkForProps( obj ); + if ( obj['content'] ) { + msg = obj['content']; + } else if ( obj['html_id'] ) { // id of page element + var el = document.getElementById( obj['html_id'] ); + if (el) msg = el.innerHTML; + } else { + msg = obj; // wrapFn will obtain props from obj + } + } + } + this.writeTip(msg); + }, + + writeTip: function(msg, bReqFlag) { + if ( this.pendingReq && this.respRecd && !bReqFlag ) return; + msg = this.wrapFn(msg); this.tip.innerHTML = msg; + }, + + positionRelEvent: function(e, tgt) { + var _this = dw_Tooltip; + if (typeof e == 'object') { // event + if ( e.type == 'mouseover' || e.type == 'mousemove' ) { + _this.evX = _this.getMouseEventX(e); + _this.evY = _this.getMouseEventY(e); + } else { // focus + var pos = dw_getPageOffsets( tgt ); + _this.evX = pos.x; + _this.evY = pos.y; + } + } + + var coords = _this.calcPosCoords(e, tgt); + _this.setPosition(coords.x, coords.y); + }, + + calcPosCoords: function(e, tgt) { + var x = this.evX; var y = this.evY; var xXd, yXd; + var maxX = this.getMaxX(); var maxY = this.getMaxY(); // tip width/height too + + var tx = x + this.offX; + var altx = x - ( this.width + this.offX ); + var spL = x - dw_Viewport.scrollX > dw_Viewport.width/2; + + if ( typeof e == 'object' && e.type && ( e.type == 'focus' || e.type == 'focusin' ) ) { + var tgtWidth = tgt.offsetWidth; + if ( tx + tgtWidth < maxX ) { + x = this.evX = x + tgtWidth; + tx += tgtWidth; + } else if (tx + 20 < maxX ) { + x = this.evX = x + 20; + tx += 20 + } + y = this.evY = y + 10; + } + + var ty = y + this.offY; + var alty = y - ( this.height + this.offY ); + var spA = y - dw_Viewport.scrollY > dw_Viewport.height/2; + + if ( !this.Left && tx < maxX ) { + x = tx; + } else if ( ( this.Left && altx >= dw_Viewport.scrollX ) || + ( this.jumpLeft && tx >= maxX && altx >= dw_Viewport.scrollX ) ) { + x = altx; + } else if ( ( this.Left && altx < dw_Viewport.scrollX ) || + ( !this.Left && this.jumpLeft && altx < dw_Viewport.scrollX && spL ) ) { + x = dw_Viewport.scrollX; // place at left edge + xXd = 'Left'; // check later whether yXd too + } else if ( !this.Left && tx >= maxX && ( !this.jumpLeft || + ( this.jumpLeft && altx < dw_Viewport.scrollX && !spL ) ) ) { + x = maxX; xXd = 'Right'; + } + + if ( !this.Above && ty < maxY ) { + y = ty; + } else if ( ( this.Above && alty >= dw_Viewport.scrollY ) || + ( this.jumpAbove && ty >= maxY && alty >= dw_Viewport.scrollY ) ) { + y = alty; + } else if ( ( this.Above && alty < dw_Viewport.scrollY ) || + ( !this.Above && this.jumpAbove && alty < dw_Viewport.scrollY && spA ) ) { + y = dw_Viewport.scrollY; // place at top + yXd = 'Above'; + } else if ( !this.Above && ty >= maxY && ( !this.jumpAbove || + ( this.jumpAbove && alty < dw_Viewport.scrollY && !spA ) ) ) { + y = maxY; yXd = 'Below'; + } + + if ( xXd && yXd ) { // over link (will flicker) calc least distance to uncover + var dx = (xXd == 'Left')? dw_Viewport.scrollX - altx: tx - maxX; + var dy = (yXd == 'Above')? dw_Viewport.scrollY - alty: ty - maxY; + if ( dx <= dy ) { + x = (xXd == 'Left')? altx: tx; + } else { + y = (yXd == 'Above')? alty: ty; + } + } + return { x: x, y: y } + }, + + adjust: function() { + var _this = dw_Tooltip; + var imgs = _this.tip.getElementsByTagName('img'); + var img = imgs.length? imgs[imgs.length - 1]: null; + checkComplete(); + + function checkComplete() { + if ( !_this.active ) return; + _this.positionFn(); + if (img && !img.complete) { + setTimeout( checkComplete, 50); + } + } + }, + + setPosition: function(x, y) { + this.tip.style.left = x + 'px'; + this.tip.style.top = y + 'px'; + this.setOverlay(); this.on_position(); + }, + + show: function() { + var _this = dw_Tooltip; + _this.tip.style.visibility = 'visible'; + if ( _this.shim ) _this.shim.style.visibility = 'visible'; + _this.on_show(); + }, + + deactivate: function(e) { + var _this = dw_Tooltip; if (!_this.tip || !_this.active || _this.sticky ) return; + e = e? e: window.event; + if (e.type && e.type == 'mouseout' && !dw_mouseleave(e, _this.actuator) ) return; + _this.clearTimer('timer'); _this.clearTimer('hoverTimer'); + + if ( _this.hoverable ) { // delayed call to hide (time to check if hovered over tip) + _this.hoverTimer = setTimeout( _this.hide, _this.hoverDelay ); + return; + } + if ( _this.duration ) { + _this.timer = setTimeout( _this.hide, _this.duration ); + return; + } + _this.timer = setTimeout( _this.hide, _this.hideDelay ); + }, + + hide: function() { + var _this = dw_Tooltip; if (!_this.tip) return; + _this.tip.style.visibility = 'hidden'; + if ( _this.shim ) _this.shim.style.visibility = 'hidden'; + _this.handleRestore(); _this.on_hide(); + }, + + handleOptions: function(e) { + this.coordinateOptions(); + if ( this.klass ) { this.tip.className = this.klass; } + if ( this.hoverable ) { + this.tip.onmouseout = dw_Tooltip.tipOutCheck; + this.tip.onmouseover = function() { dw_Tooltip.clearTimer('hoverTimer'); } + } + if ( this.followMouse && !this.hoverable && !(e.type == 'focus' || e.type == 'focusin') ) { + dw_Event.add(document, 'mousemove', this.positionRelEvent); + } + + if ( this.sticky || this.duration ) { + dw_Event.add( document, "mouseup", dw_Tooltip.checkDocClick ); + } + }, + + coordinateOptions: function() { + if ( this.sticky || this.hoverable || this.duration ) { this.followMouse = false; } + if ( this.sticky ) { this.hoverable = false; this.duration = 0; } + if ( this.hoverable ) { this.duration = 0; } + if ( this.positionFn != this.positionRelEvent ) this.followMouse = false; + }, + + handleRestore: function() { + if ( this.followMouse ) { + dw_Event.remove(document, 'mousemove', this.positionRelEvent); + } + if ( this.sticky || this.duration ) { + dw_Event.remove( document, "mouseup", dw_Tooltip.checkDocClick, false ); + } + this.tip.onmouseover = this.tip.onmouseout = function() {} + + if ( this.resetFlag ) this.setDefaults(); + this.writeTip(''); + + this.active = false; this.actuator = null; + this.tip.style.width = ''; + this.restored = true; + }, + + // first class name is actuatorClass, second class would point to content + getTipClass: function(cls) { + if (!cls) return ''; var c = ''; + var classes = cls.split(/\s+/); + if ( classes[0] == this.actuatorClass && classes[1] ) { + c = classes[1]; + } + return c; // return second class name or '' + }, + + checkForProps: function(obj) { + var list = ['jumpAbove', 'jumpLeft', 'Above', 'Left', 'sticky', 'duration', + 'hoverable', 'followMouse', 'klass', 'positionFn', 'wrapFn']; + for (var i=0; list[i]; i++) { + if ( typeof obj[ list[i] ] != 'undefined' ) { + this[ list[i] ] = obj[ list[i] ]; + this.resetFlag = true; + } + } + }, + + tipOutCheck: function(e) { // hover tip + var _this = dw_Tooltip; e = dw_Event.DOMit(e); + var tip = this; // assigned to onmouseover property of tip + if ( dw_mouseleave(e, tip) ) { + _this.timer = setTimeout( _this.hide, _this.hideDelay); + } + }, + + checkEscKey: function(e) { // for sticky, duration, and onfocus activation + e = e? e: window.event; if ( e.keyCode == 27 ) dw_Tooltip.hide(); + }, + + checkDocClick: function(e) { + if ( !dw_Tooltip.active ) return; + var tgt = dw_Event.getTarget(e); + // hide tooltip if you click anywhere in the document + // except on the tooltip, unless that click is on the tooltip's close box + var tip = document.getElementById(dw_Tooltip.tipID); + if ( tgt == tip || dw_contained(tgt, tip) ) { + if ( tgt.tagName && tgt.tagName.toLowerCase() == "img" ) tgt = tgt.parentNode; + if ( tgt.tagName.toLowerCase() != "a" || tgt.href.indexOf("dw_Tooltip.hide") != -1 ) return; + } + // slight delay to avoid crossing onfocus activation and doc click hide + dw_Tooltip.timer = setTimeout( dw_Tooltip.hide, 50); + }, + + // check need for and support of iframe shim (for ie win and select lists) + checkOverlaySupport: function() { + if ( navigator.userAgent.indexOf("Windows") != -1 && + typeof document.body != "undefined" && + typeof document.body.insertAdjacentHTML != "undefined" && + !window.opera && navigator.appVersion.indexOf("MSIE 5.0") == -1 + ) return true; + return false; + }, + + prepOverlay: function() { + document.body.insertAdjacentHTML("beforeEnd", '<iframe id="tipShim" src="javascript: false" style="position:absolute; left:0; top:0; z-index:500; visibility:hidden" scrolling="no" frameborder="0"></iframe>'); + this.shim = document.getElementById('tipShim'); + if (this.shim && this.tip) { + this.shim.style.width = this.tip.offsetWidth + "px"; + this.shim.style.height = this.tip.offsetHeight + "px"; + } + }, + + setOverlay: function() { // position and dimensions + if ( this.shim ) { + this.shim.style.left = this.tip.style.left; + this.shim.style.top = this.tip.style.top; + this.shim.style.width = this.tip.offsetWidth + "px"; + this.shim.style.height = this.tip.offsetHeight + "px"; + } + }, + + clearTimer: function(timer) { + if ( dw_Tooltip[timer] ) { clearTimeout( dw_Tooltip[timer] ); dw_Tooltip[timer] = 0; } + }, + + getWidth: function() { return this.width = this.tip.offsetWidth; }, + getHeight: function() { return this.height = this.tip.offsetHeight; }, + getMaxX: function() { return dw_Viewport.width + dw_Viewport.scrollX - this.getWidth() - 1; }, + getMaxY: function() { return dw_Viewport.height + dw_Viewport.scrollY - this.getHeight() - 1; }, + getMouseEventX: function(e) { return e.pageX? e.pageX: e.clientX + dw_Viewport.scrollX; }, + getMouseEventY: function(e) { return e.pageY? e.pageY: e.clientY + dw_Viewport.scrollY; } + +} + +// code for event delegation +dw_Tooltip.initHandlers = function () { + dw_Event.add( document, 'mouseover', dw_Tooltip.checkActuatorMouseover); + dw_Event.add( document, "keydown", dw_Tooltip.checkEscKey, true ); // for sticky + dw_Event.add( window, 'blur', dw_Tooltip.deactivate, true ); + dw_Event.add( window, 'unload', dw_Tooltip.deactivate, true ); // firefox needs + + // see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html + if ( dw_Tooltip.activateOnfocus ) { + document.onfocusin = dw_Tooltip.checkActuatorFocus; // ie + if ( window.addEventListener ) { + dw_Event.add(document, 'focus', dw_Tooltip.checkActuatorFocus, true); + } + } +} + +dw_Tooltip.checkActuatorMouseover = function (e) { + var tgt = dw_Event.getTarget(e); var tipAct = null; + // limit number of loops + var ctr = 0; var maxCnt = dw_Tooltip.maxLoops; + do { + if ( tipAct = dw_Tooltip.getActuatorInfo(tgt) ) { + var msg = tipAct.msg; var id = tipAct.id; + dw_Tooltip.activate(e, tgt, msg, id); + if ( window.attachEvent ) { // avoid multiples for ie (?) + dw_Event.remove( tgt, 'mouseout', dw_Tooltip.deactivate); + } + dw_Event.add( tgt, 'mouseout', dw_Tooltip.deactivate); + break; + } + ctr++; + } while ( ctr < maxCnt && (tgt = tgt.parentNode) ); + +} + +dw_Tooltip.checkActuatorFocus = function (e) { + e = e? e: window.event; var tipAct = null; + var tgt = dw_Event.getTarget(e); + if ( tgt && (tipAct = dw_Tooltip.getActuatorInfo(tgt) ) ) { + if ( dw_Tooltip.active && tgt == dw_Tooltip.actuator ) { + return; // if already activated onmouseover + } + var msg = tipAct.msg; var id = tipAct.id; + dw_Tooltip.activate(e, tgt, msg, id); + if ( window.attachEvent ) { + tgt.onfocusout = dw_Tooltip.deactivate; + } else { + dw_Event.add( tgt, 'blur', dw_Tooltip.deactivate, true); + } + } +} + +// Check whether the target is an actuator and the content can be located +// Either the content itself or the identifier in content_vars will be returned in obj {msg: msg, id: id} +dw_Tooltip.getActuatorInfo = function (tgt) { + var qual = dw_Tooltip.defaultProps['actuatorQual'] || 'actuatorClass'; + var source = dw_Tooltip.defaultProps['content_source'] || 'content_vars'; + var msg = '', id = ''; + dw_Tooltip.resetReqFlags(); + switch (qual) { + case 'actuatorClass' : + var cls = dw_Tooltip.getTipClass(tgt.className); + if (!cls) break; + if ( source == 'content_vars' ) { + id = (dw_Tooltip.content_vars && dw_Tooltip.content_vars[cls])? cls: ''; + } else if ( source == 'class_id' ) { + var el = document.getElementById(cls); + if (el) msg = el.innerHTML; + } + break; + case 'queryVal' : + var queryVal = dw_Tooltip.defaultProps['queryVal']; + var val = queryVal? dw_getValueFromQueryString(queryVal, tgt ): ''; + id = (val && dw_Tooltip.content_vars && dw_Tooltip.content_vars[val])? val: ''; + // Even if the content source is ajax, would check content_vars (see below) + // dw_updateTooltip should be set up to save results in content_vars + if ( val && source == 'ajax' && !id ) { + // Something to display in the tooltip while awaiting response. Empty string won't suffice! + msg = 'Retrieving info ...'; + dw_Tooltip.pendingReq = true; + var queryData = encodeURIComponent(queryVal) + '=' + encodeURIComponent(val); + dw_TooltipRequest( queryData, val ); // val passed as means to save response + } + break; + case 'id' : + id = (tgt.id && dw_Tooltip.content_vars && dw_Tooltip.content_vars[tgt.id])? tgt.id: ''; + break; + } + //if ( id && !msg ) { // check content_vars (for previously saved ajax result, or more complex data for ajax request) } + if ( id || msg ) { + return {msg: msg, id: id} + } + return false; +} + +// check so don't overwrite response if already received (localhost speed) +dw_Tooltip.resetReqFlags = function () { + this.respRecd = false; + this.pendingReq = false; +} + +///////////////////////////////////////////////////////////////////// +// Helper functions +function dw_mouseleave(e, oNode) { + e = dw_Event.DOMit(e); + var toEl = e.relatedTarget? e.relatedTarget: e.toElement? e.toElement: null; + if ( oNode != toEl && !dw_contained(toEl, oNode) ) { + return true; + } + return false; +} + +function dw_contained(oNode, oCont) { + if (!oNode) return; // in case alt-tab away while hovering (prevent error) + while ( oNode = oNode.parentNode ) if ( oNode == oCont ) return true; + return false; +} + +// Get position of element in page (treacherous cross-browser territory! Don't expect perfect results) +// can get weird results in ie +function dw_getPageOffsets(el) { + var left = 0, top = 0; + do { + left += el.offsetLeft; + top += el.offsetTop; + } while (el = el.offsetParent); + return { x:left, y:top }; +} + +// obj: link or window.location +function dw_getValueFromQueryString(name, obj) { + obj = obj? obj: window.location; + if (obj.search && obj.search.indexOf(name != -1) ) { + var pairs = obj.search.slice(1).split("&"); // name/value pairs + var set; + for (var i=0; pairs[i]; i++) { + set = pairs[i].split("="); // Check each pair for match on name + if ( set[0] == name && set[1] ) { + return set[1]; + } + } + } + return ''; +} + diff --git a/Database/CoolRunQuery/html/js/dw_tooltip_aux.js b/Database/CoolRunQuery/html/js/dw_tooltip_aux.js new file mode 100755 index 00000000000..613b40c7530 --- /dev/null +++ b/Database/CoolRunQuery/html/js/dw_tooltip_aux.js @@ -0,0 +1,178 @@ +/************************************************************************* + This code is from Dynamic Web Coding at dyn-web.com + Copyright 2008 by Sharon Paine + See Terms of Use at www.dyn-web.com/business/terms.php + regarding conditions under which you may use this code. + This notice must be retained in the code as is! + + for use with dw_tooltip.js + Version date: Aug 2008 (dw_Event.add instead of addLoadEvent for onload ) +*************************************************************************/ + + +// Used when the tooltip content is in HTML elements with tipContent class attached +dw_Tooltip.writeStyleRule = function() { + if ( document.createElement && document.getElementsByTagName && + ( document.addEventListener || document.attachEvent ) ) { + document.write('<style type="text/css" media="screen">.tipContent { display:none; }</style>'); + } +} + +/////////////////////////////////////////////////////////////////////////////////// +// Initialization: init tooltip and set up event delegation + +dw_Event.add( window, 'load', dw_Tooltip.init ); +dw_Event.add( window, 'load', dw_Tooltip.initHandlers ); + +// removed in favor of event delegation +function dw_initShowTip() {} // empty fn to avoid errors + +// fn's for ajax in head of ajax demo +function dw_TooltipRequest() {} + +///////////////////////////////////////////////////////////////////// +// Positioning algorithms + +dw_Tooltip.positionWindowCenter = function() { + var x = Math.round( (dw_Viewport.width - dw_Tooltip.tip.offsetWidth)/2 ) + dw_Viewport.scrollX; + var y = Math.round( (dw_Viewport.height - dw_Tooltip.tip.offsetHeight)/2 ) + dw_Viewport.scrollY; + dw_Tooltip.setPosition(x,y); +} + +// more later or TBA + +///////////////////////////////////////////////////////////////////// +// formatting and display functions + +// for style sheet specs: id for stickyTable, +// stickyBar class for tr, div classes: stickyTitle, stickyContent (inside td's) +dw_Tooltip.wrapSticky = function(str, title) { + title = title || ''; + var src = dw_Tooltip.defaultProps['closeBoxImage']; + var msg = '<table id="stickyTable" border="0" cellpadding="0" cellspacing="0" width="100%"><tr class="stickyBar">' + + '<td><div class="stickyTitle">' + title + '</div></td>' + + '<td style="text-align:right"><a href="javascript: void dw_Tooltip.hide()">' + + '<img style="float:right" src="' + src + '" border="0" /></a></td></tr>' + + '<tr><td colspan="2"><div class="stickyContent">' + str + '</div></td></tr></table>'; + return msg; +} + +// optional caption, optional width supported by all these wrapFn's + +dw_Tooltip.wrapToWidth = function(obj) { + if (!obj) return ''; var str = obj['str']; + var caption = obj['caption'] || ''; + if ( this.sticky && this.defaultProps['showCloseBox'] ) { + str = dw_Tooltip.wrapSticky(str, caption ); + } else { + if (caption) { str = '<div class="caption">' + obj['caption'] + '</div>' + str; } + } + if ( obj['w'] ) this.setTipWidth( obj['w'] ); + return str; +} + +// w, h in obj are width and height of image +dw_Tooltip.wrapImageToWidth = function(obj) { + if (!obj) return ''; dw_getImage( obj['img'] ); + var caption = obj['caption'] || ''; var w = obj['w']; + var str = '<img src="' + obj['img'] + '" width="' +w + '" height="' + obj['h'] + '" alt="">'; + if ( this.sticky && this.defaultProps['showCloseBox'] ) { + str = dw_Tooltip.wrapSticky(str, caption ); + w += 8; // attempt to account for padding etc of inner wrapper + } else { + if (caption) { str = '<div class="caption">' + obj['caption'] + '</div>' + str; } + } + if (w) this.setTipWidth(w); + return str; +} + +// Image and text side by side +// w is width to set tipDiv +dw_Tooltip.wrapTextByImage = function(obj) { + if (!obj) return ''; dw_getImage( obj['img'] ); + var caption = obj['caption'] || ''; + var str = '<table cellpadding="0" cellspacing="0" border="0"><tr>' + + '<td><div class="txt">' + obj['txt'] + '</div></td>' + + '<td><div class="img"><img src="' + obj['img'] + '" /></div>' + + '</td></tr></table>'; + + if ( this.sticky && this.defaultProps['showCloseBox'] ) { + str = dw_Tooltip.wrapSticky(str, caption ); + } else { + if (caption) { str = '<div class="caption">' + obj['caption'] + '</div>' + str; } + } + if ( obj['w'] ) this.setTipWidth( obj['w'] ); + return str; +} + +dw_Tooltip.wrapImageOverText = function(obj) { + if (!obj) return ''; dw_getImage( obj['img'] ); + var caption = obj['caption'] || ''; + var str = '<div class="img"><img src="' + obj['img'] + '" /></div><div class="txt">' + obj['txt'] + '</div>'; + if ( this.sticky && this.defaultProps['showCloseBox'] ) { + str = dw_Tooltip.wrapSticky(str, caption ); + } else { + if (caption) { str = '<div class="caption">' + obj['caption'] + '</div>' + str; } + } + if ( obj['w'] ) this.setTipWidth( obj['w'] ); + return str; +} + +dw_Tooltip.wrapTextOverImage = function(obj) { + if (!obj) return ''; dw_getImage( obj['img'] ); + var caption = obj['caption'] || ''; + var str = '<div class="txt">' + obj['txt'] + '</div><div class="img"><img src="' + obj['img'] + '" /></div>'; + if ( this.sticky && this.defaultProps['showCloseBox'] ) { + str = dw_Tooltip.wrapSticky(str, caption ); + } else { + if (caption) { str = '<div class="caption">' + obj['caption'] + '</div>' + str; } + } + if ( obj['w'] ) this.setTipWidth( obj['w'] ); + return str; +} + +// several functions include option of setting width +dw_Tooltip.setTipWidth = function(w) { + w += dw_backCompatWidth( this.tip ); // in case padding and border set on tipDiv + this.tip.style.width = w + "px"; +} + +///////////////////////////////////////////////////////////////////// +// a few utility functions + +function dw_getImage(src) { + var img = new Image(); + img.src = src; +} + +// To obtain padding and border for setting width on an element +function dw_backCompatWidth(el) { + var val = 0; + if ( el.currentStyle && !window.opera && (document.compatMode == null || document.compatMode == "BackCompat") ) { + var p = parseInt( dw_getCurrentStyle(el, 'paddingLeft') ) + parseInt( dw_getCurrentStyle(el, 'paddingRight') ); + var b = parseInt( dw_getCurrentStyle(el, 'borderLeftWidth') ) + parseInt( dw_getCurrentStyle(el, 'borderRightWidth') ) + val = p + b; + } + return val; +} + +// prop must be camelCase (e.g., paddingLeft, borderLeftWidth) +function dw_getCurrentStyle(el, prop) { + var val = ''; + if (document.defaultView && document.defaultView.getComputedStyle) { + val = document.defaultView.getComputedStyle(el, null)[prop]; + } else if (el.currentStyle) { + val = el.currentStyle[prop]; + // from jquery, dean edwards, see http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + if ( !/^\d+(px)?$/i.test(val) && /^\d/.test(val) ) { + var style = el.style.left; + var runtimeStyle = el.runtimeStyle.left; + el.runtimeStyle.left = el.currentStyle.left; + el.style.left = val || 0; + val = el.style.pixelLeft + "px"; + el.style.left = style; + el.runtimeStyle.left = runtimeStyle; + } + } + return val; +} diff --git a/Database/CoolRunQuery/html/js/dw_viewport.js b/Database/CoolRunQuery/html/js/dw_viewport.js new file mode 100755 index 00000000000..e35585e0d7b --- /dev/null +++ b/Database/CoolRunQuery/html/js/dw_viewport.js @@ -0,0 +1,61 @@ +/************************************************************************* + dw_viewport.js + free code from dyn-web.com + version date: mar 2008 +*************************************************************************/ + +var dw_Viewport = { + getWinWidth: function () { + this.width = 0; + if (window.innerWidth) + this.width = window.innerWidth - 18; + else if (document.documentElement && document.documentElement.clientWidth) + this.width = document.documentElement.clientWidth; + else if (document.body && document.body.clientWidth) + this.width = document.body.clientWidth; + return this.width; + }, + + getWinHeight: function () { + this.height = 0; + if (window.innerHeight) + this.height = window.innerHeight - 18; + else if (document.documentElement && document.documentElement.clientHeight) + this.height = document.documentElement.clientHeight; + else if (document.body && document.body.clientHeight) + this.height = document.body.clientHeight; + return this.height; + }, + + getScrollX: function () { + this.scrollX = 0; + if (typeof window.pageXOffset == "number") + this.scrollX = window.pageXOffset; + else if (document.documentElement && document.documentElement.scrollLeft) + this.scrollX = document.documentElement.scrollLeft; + else if (document.body && document.body.scrollLeft) + this.scrollX = document.body.scrollLeft; + else if (window.scrollX) + this.scrollX = window.scrollX; + return this.scrollX; + }, + + getScrollY: function () { + this.scrollY = 0; + if (typeof window.pageYOffset == "number") + this.scrollY = window.pageYOffset; + else if (document.documentElement && document.documentElement.scrollTop) + this.scrollY = document.documentElement.scrollTop; + else if (document.body && document.body.scrollTop) + this.scrollY = document.body.scrollTop; + else if (window.scrollY) + this.scrollY = window.scrollY; + return this.scrollY; + }, + + getAll: function () { + this.getWinWidth(); this.getWinHeight(); + this.getScrollX(); this.getScrollY(); + } + +} diff --git a/Database/CoolRunQuery/html/js/jquery.min.js b/Database/CoolRunQuery/html/js/jquery.min.js new file mode 100644 index 00000000000..a0fb8836089 --- /dev/null +++ b/Database/CoolRunQuery/html/js/jquery.min.js @@ -0,0 +1,19 @@ +/* + * jQuery JavaScript Library v1.3.2 + * http://jquery.com/ + * + * Copyright (c) 2009 John Resig + * Dual licensed under the MIT and GPL licenses. + * http://docs.jquery.com/License + * + * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) + * Revision: 6246 + */ +(function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.isArray(E)?E:o.makeArray(E))},selector:"",jquery:"1.3.2",size:function(){return this.length},get:function(E){return E===g?Array.prototype.slice.call(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,sort:[].sort,splice:[].splice,find:function(E){if(this.length===1){var F=this.pushStack([],"find",E);F.length=0;o.find(E,this[0],F);return F}else{return this.pushStack(o.unique(o.map(this,function(G){return o.find(E,G)})),"find",E)}},clone:function(G){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.outerHTML;if(!I){var J=this.ownerDocument.createElement("div");J.appendChild(this.cloneNode(true));I=J.innerHTML}return o.clean([I.replace(/ jQuery\d+="(?:\d+|null)"/g,"").replace(/^\s*/,"")])[0]}else{return this.cloneNode(true)}});if(G===true){var H=this.find("*").andSelf(),F=0;E.find("*").andSelf().each(function(){if(this.nodeName!==H[F].nodeName){return}var I=o.data(H[F],"events");for(var K in I){for(var J in I[K]){o.event.add(this,K,I[K][J],I[K][J].data)}}F++})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var G=o.expr.match.POS.test(E)?o(E):null,F=0;return this.map(function(){var H=this;while(H&&H.ownerDocument){if(G?G.index(H)>-1:o(H).is(E)){o.data(H,"closest",F);return H}H=H.parentNode;F++}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F<J;F++){var G=M[F];if(G.selected){K=o(G).val();if(H){return K}L.push(K)}}return L}return(E.value||"").replace(/\r/g,"")}return g}if(typeof K==="number"){K+=""}return this.each(function(){if(this.nodeType!=1){return}if(o.isArray(K)&&/radio|checkbox/.test(this.type)){this.checked=(o.inArray(this.value,K)>=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g,""):null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=o.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild;if(H){for(var G=0,E=this.length;G<E;G++){L.call(K(this[G],H),this.length>1||G>0?I.cloneNode(true):I)}}if(F){o.each(F,z)}}return this;function K(N,O){return M&&o.nodeName(N,"table")&&o.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H<I;H++){if((G=arguments[H])!=null){for(var F in G){var K=J[F],L=G[F];if(J===L){continue}if(E&&L&&typeof L==="object"&&!L.nodeType){J[F]=o.extend(E,K||(L.length!=null?[]:{}),L)}else{if(L!==g){J[F]=L}}}}}return J};var b=/z-?index|font-?weight|opacity|zoom|line-?height/i,q=document.defaultView||{},s=Object.prototype.toString;o.extend({noConflict:function(E){l.$=p;if(E){l.jQuery=y}return o},isFunction:function(E){return s.call(E)==="[object Function]"},isArray:function(E){return s.call(E)==="[object Array]"},isXMLDoc:function(E){return E.nodeType===9&&E.documentElement.nodeName!=="HTML"||!!E.ownerDocument&&o.isXMLDoc(E.ownerDocument)},globalEval:function(G){if(G&&/\S/.test(G)){var F=document.getElementsByTagName("head")[0]||document.documentElement,E=document.createElement("script");E.type="text/javascript";if(o.support.scriptEval){E.appendChild(document.createTextNode(G))}else{E.text=G}F.insertBefore(E,F.firstChild);F.removeChild(E)}},nodeName:function(F,E){return F.nodeName&&F.nodeName.toUpperCase()==E.toUpperCase()},each:function(G,K,F){var E,H=0,I=G.length;if(F){if(I===g){for(E in G){if(K.apply(G[E],F)===false){break}}}else{for(;H<I;){if(K.apply(G[H++],F)===false){break}}}}else{if(I===g){for(E in G){if(K.call(G[E],E,G[E])===false){break}}}else{for(var J=G[0];H<I&&K.call(J,H,J)!==false;J=G[++H]){}}}return G},prop:function(H,I,G,F,E){if(o.isFunction(I)){I=I.call(H,F)}return typeof I==="number"&&G=="curCSS"&&!b.test(E)?I+"px":I},className:{add:function(E,F){o.each((F||"").split(/\s+/),function(G,H){if(E.nodeType==1&&!o.className.has(E.className,H)){E.className+=(E.className?" ":"")+H}})},remove:function(E,F){if(E.nodeType==1){E.className=F!==g?o.grep(E.className.split(/\s+/),function(G){return !o.className.has(F,G)}).join(" "):""}},has:function(F,E){return F&&o.inArray(E,(F.className||F).toString().split(/\s+/))>-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(H,F,J,E){if(F=="width"||F=="height"){var L,G={position:"absolute",visibility:"hidden",display:"block"},K=F=="width"?["Left","Right"]:["Top","Bottom"];function I(){L=F=="width"?H.offsetWidth:H.offsetHeight;if(E==="border"){return}o.each(K,function(){if(!E){L-=parseFloat(o.curCSS(H,"padding"+this,true))||0}if(E==="margin"){L+=parseFloat(o.curCSS(H,"margin"+this,true))||0}else{L-=parseFloat(o.curCSS(H,"border"+this+"Width",true))||0}})}if(H.offsetWidth!==0){I()}else{o.swap(H,G,I)}return Math.max(0,Math.round(L))}return o.curCSS(H,F,J)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,S){if(typeof S==="number"){S+=""}if(!S){return}if(typeof S==="string"){S=S.replace(/(<(\w+)[^>]*?)\/>/g,function(U,V,T){return T.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?U:V+"></"+T+">"});var O=S.replace(/^\s+/,"").substring(0,10).toLowerCase();var Q=!O.indexOf("<opt")&&[1,"<select multiple='multiple'>","</select>"]||!O.indexOf("<leg")&&[1,"<fieldset>","</fieldset>"]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"<table>","</table>"]||!O.indexOf("<tr")&&[2,"<table><tbody>","</tbody></table>"]||(!O.indexOf("<td")||!O.indexOf("<th"))&&[3,"<table><tbody><tr>","</tr></tbody></table>"]||!O.indexOf("<col")&&[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"]||!o.support.htmlSerialize&&[1,"div<div>","</div>"]||[0,"",""];L.innerHTML=Q[1]+S+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var R=/<tbody/i.test(S),N=!O.indexOf("<table")&&!R?L.firstChild&&L.firstChild.childNodes:Q[1]=="<table>"&&!R?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(S)){L.insertBefore(K.createTextNode(S.match(/^\s*/)[0]),L.firstChild)}S=o.makeArray(L.childNodes)}if(S.nodeType){G.push(S)}else{G=o.merge(G,S)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E<F;E++){if(H[E]===G){return E}}return -1},merge:function(H,E){var F=0,G,I=H.length;if(!o.support.getAll){while((G=E[F++])!=null){if(G.nodeType!=8){H[I++]=G}}}else{while((G=E[F++])!=null){H[I++]=G}}return H},unique:function(K){var F=[],E={};try{for(var G=0,H=K.length;G<H;G++){var J=o.data(K[G]);if(!E[J]){E[J]=true;F.push(K[G])}}}catch(I){F=K}return F},grep:function(F,J,E){var G=[];for(var H=0,I=F.length;H<I;H++){if(!E!=!J(F[H],H)){G.push(F[H])}}return G},map:function(E,J){var F=[];for(var G=0,H=E.length;G<H;G++){var I=J(E[G],G);if(I!=null){F[F.length]=I}}return F.concat.apply([],F)}});var C=navigator.userAgent.toLowerCase();o.browser={version:(C.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)||[0,"0"])[1],safari:/webkit/.test(C),opera:/opera/.test(C),msie:/msie/.test(C)&&!/opera/.test(C),mozilla:/mozilla/.test(C)&&!/(compatible|webkit)/.test(C)};o.each({parent:function(E){return E.parentNode},parents:function(E){return o.dir(E,"parentNode")},next:function(E){return o.nth(E,2,"nextSibling")},prev:function(E){return o.nth(E,2,"previousSibling")},nextAll:function(E){return o.dir(E,"nextSibling")},prevAll:function(E){return o.dir(E,"previousSibling")},siblings:function(E){return o.sibling(E.parentNode.firstChild,E)},children:function(E){return o.sibling(E.firstChild)},contents:function(E){return o.nodeName(E,"iframe")?E.contentDocument||E.contentWindow.document:o.makeArray(E.childNodes)}},function(E,F){o.fn[E]=function(G){var H=o.map(this,F);if(G&&typeof G=="string"){H=o.multiFilter(G,H)}return this.pushStack(o.unique(H),E,G)}});o.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(E,F){o.fn[E]=function(G){var J=[],L=o(G);for(var K=0,H=L.length;K<H;K++){var I=(K>0?this.clone(true):this).get();o.fn[F].apply(o(L[K]),I);J=J.concat(I)}return this.pushStack(J,E,G)}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(this).children().remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}}); +/* + * Sizzle CSS Selector Engine - v0.9.3 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa<ab.length;aa++){if(ab[aa]===ab[aa-1]){ab.splice(aa--,1)}}}}}return ab};F.matches=function(T,U){return F(T,null,null,U)};F.find=function(aa,T,ab){var Z,X;if(!aa){return[]}for(var W=0,V=I.order.length;W<V;W++){var Y=I.order[W],X;if((X=I.match[Y].exec(aa))){var U=RegExp.leftContext;if(U.substr(U.length-1)!=="\\"){X[1]=(X[1]||"").replace(/\\/g,"");Z=I.find[Y](X,T,ab);if(Z!=null){aa=aa.replace(I.match[Y],"");break}}}}if(!Z){Z=T.getElementsByTagName("*")}return{set:Z,expr:aa}};F.filter=function(ad,ac,ag,W){var V=ad,ai=[],aa=ac,Y,T,Z=ac&&ac[0]&&Q(ac[0]);while(ad&&ac.length){for(var ab in I.filter){if((Y=I.match[ab].exec(ad))!=null){var U=I.filter[ab],ah,af;T=false;if(aa==ai){ai=[]}if(I.preFilter[ab]){Y=I.preFilter[ab](Y,aa,ag,ai,W,Z);if(!Y){T=ah=true}else{if(Y===true){continue}}}if(Y){for(var X=0;(af=aa[X])!=null;X++){if(af){ah=U(af,Y,X,aa);var ae=W^!!ah;if(ag&&ah!=null){if(ae){T=true}else{aa[X]=false}}else{if(ae){ai.push(af);T=true}}}}}if(ah!==g){if(!ag){aa=ai}ad=ad.replace(I.match[ab],"");if(!T){return[]}break}}}if(ad==V){if(T==null){throw"Syntax error, unrecognized expression: "+ad}else{break}}V=ad}return aa};var I=F.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(T){return T.getAttribute("href")}},relative:{"+":function(aa,T,Z){var X=typeof T==="string",ab=X&&!/\W/.test(T),Y=X&&!ab;if(ab&&!Z){T=T.toUpperCase()}for(var W=0,V=aa.length,U;W<V;W++){if((U=aa[W])){while((U=U.previousSibling)&&U.nodeType!==1){}aa[W]=Y||U&&U.nodeName===T?U||false:U===T}}if(Y){F.filter(T,aa,true)}},">":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V<T;V++){var Y=Z[V];if(Y){var W=Y.parentNode;Z[V]=W.nodeName===U?W:false}}}else{for(var V=0,T=Z.length;V<T;V++){var Y=Z[V];if(Y){Z[V]=X?Y.parentNode:Y.parentNode===U}}if(X){F.filter(U,Z,true)}}},"":function(W,U,Y){var V=L++,T=S;if(!U.match(/\W/)){var X=U=Y?U:U.toUpperCase();T=P}T("parentNode",U,V,W,X,Y)},"~":function(W,U,Y){var V=L++,T=S;if(typeof U==="string"&&!U.match(/\W/)){var X=U=Y?U:U.toUpperCase();T=P}T("previousSibling",U,V,W,X,Y)}},find:{ID:function(U,V,W){if(typeof V.getElementById!=="undefined"&&!W){var T=V.getElementById(U[1]);return T?[T]:[]}},NAME:function(V,Y,Z){if(typeof Y.getElementsByName!=="undefined"){var U=[],X=Y.getElementsByName(V[1]);for(var W=0,T=X.length;W<T;W++){if(X[W].getAttribute("name")===V[1]){U.push(X[W])}}return U.length===0?null:U}},TAG:function(T,U){return U.getElementsByTagName(T[1])}},preFilter:{CLASS:function(W,U,V,T,Z,aa){W=" "+W[1].replace(/\\/g,"")+" ";if(aa){return W}for(var X=0,Y;(Y=U[X])!=null;X++){if(Y){if(Z^(Y.className&&(" "+Y.className+" ").indexOf(W)>=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return U<T[3]-0},gt:function(V,U,T){return U>T[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W<T;W++){if(Y[W]===Z){return false}}return true}}}},CHILD:function(T,W){var Z=W[1],U=T;switch(Z){case"only":case"first":while(U=U.previousSibling){if(U.nodeType===1){return false}}if(Z=="first"){return true}U=T;case"last":while(U=U.nextSibling){if(U.nodeType===1){return false}}return true;case"nth":var V=W[2],ac=W[3];if(V==1&&ac==0){return true}var Y=W[0],ab=T.parentNode;if(ab&&(ab.sizcache!==Y||!T.nodeIndex)){var X=0;for(U=ab.firstChild;U;U=U.nextSibling){if(U.nodeType===1){U.nodeIndex=++X}}ab.sizcache=Y}var aa=T.nodeIndex-ac;if(V==0){return aa==0}else{return(aa%V==0&&aa/V>=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V<T;V++){U.push(X[V])}}else{for(var V=0;X[V];V++){U.push(X[V])}}}return U}}var G;if(document.documentElement.compareDocumentPosition){G=function(U,T){var V=U.compareDocumentPosition(T)&4?-1:U===T?0:1;if(V===0){hasDuplicate=true}return V}}else{if("sourceIndex" in document.documentElement){G=function(U,T){var V=U.sourceIndex-T.sourceIndex;if(V===0){hasDuplicate=true}return V}}else{if(document.createRange){G=function(W,U){var V=W.ownerDocument.createRange(),T=U.ownerDocument.createRange();V.selectNode(W);V.collapse(true);T.selectNode(U);T.collapse(true);var X=V.compareBoundaryPoints(Range.START_TO_END,T);if(X===0){hasDuplicate=true}return X}}}}(function(){var U=document.createElement("form"),V="script"+(new Date).getTime();U.innerHTML="<input name='"+V+"'/>";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML="<a href='#'></a>";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="<p class='TEST'></p>";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="<div class='test e'></div><div class='test'></div>";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W<V;W++){var T=ad[W];if(T){if(ab&&T.nodeType===1){T.sizcache=Y;T.sizset=W}T=T[U];var X=false;while(T){if(T.sizcache===Y){X=ad[T.sizset];break}if(T.nodeType===1&&!ac){T.sizcache=Y;T.sizset=W}if(T.nodeName===Z){X=T;break}T=T[U]}ad[W]=X}}}function S(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W<V;W++){var T=ad[W];if(T){if(ab&&T.nodeType===1){T.sizcache=Y;T.sizset=W}T=T[U];var X=false;while(T){if(T.sizcache===Y){X=ad[T.sizset];break}if(T.nodeType===1){if(!ac){T.sizcache=Y;T.sizset=W}if(typeof Z!=="string"){if(T===Z){X=true;break}}else{if(F.filter(Z,[T]).length>0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z<U;Z++){F(T,V[Z],W)}return F.filter(X,W)};o.find=F;o.filter=F.filter;o.expr=F.selectors;o.expr[":"]=o.expr.filters;F.selectors.filters.hidden=function(T){return T.offsetWidth===0||T.offsetHeight===0};F.selectors.filters.visible=function(T){return T.offsetWidth>0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F<E.length){o.event.proxy(G,E[F++])}return this.click(o.event.proxy(G,function(H){this.lastToggle=(this.lastToggle||0)%F;H.preventDefault();return E[this.lastToggle++].apply(this,arguments)||false}))},hover:function(E,F){return this.mouseenter(E).mouseleave(F)},ready:function(E){B();if(o.isReady){E.call(document,o)}else{o.readyList.push(E)}return this},live:function(G,F){var E=o.event.proxy(F);E.guid+=this.selector+G;o(document).bind(i(G,this.selector),this.selector,E);return this},die:function(F,E){o(document).unbind(i(F,this.selector),E?{guid:E.guid+this.selector+F}:null);return this}});function c(H){var E=RegExp("(^|\\.)"+H.type+"(\\.|$)"),G=true,F=[];o.each(o.data(this,"events").live||[],function(I,J){if(E.test(J.type)){var K=o(H.target).closest(J.data)[0];if(K){F.push({elem:K,fn:J})}}});F.sort(function(J,I){return o.data(J.elem,"closest")-o.data(I.elem,"closest")});o.each(F,function(){if(this.fn.call(this.elem,H,this.fn.data)===false){return(G=false)}});return G}function i(F,E){return["live",F,E.replace(/\./g,"`").replace(/ /g,"|")].join(".")}o.extend({isReady:false,readyList:[],ready:function(){if(!o.isReady){o.isReady=true;if(o.readyList){o.each(o.readyList,function(){this.call(document,o)});o.readyList=null}o(document).triggerHandler("ready")}}});var x=false;function B(){if(x){return}x=true;if(document.addEventListener){document.addEventListener("DOMContentLoaded",function(){document.removeEventListener("DOMContentLoaded",arguments.callee,false);o.ready()},false)}else{if(document.attachEvent){document.attachEvent("onreadystatechange",function(){if(document.readyState==="complete"){document.detachEvent("onreadystatechange",arguments.callee);o.ready()}});if(document.documentElement.doScroll&&l==l.top){(function(){if(o.isReady){return}try{document.documentElement.doScroll("left")}catch(E){setTimeout(arguments.callee,0);return}o.ready()})()}}}o.event.add(l,"load",o.ready)}o.each(("blur,focus,load,resize,scroll,unload,click,dblclick,mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave,change,select,submit,keydown,keypress,keyup,error").split(","),function(F,E){o.fn[E]=function(G){return G?this.bind(E,G):this.trigger(E)}});o(l).bind("unload",function(){for(var E in o.cache){if(E!=1&&o.cache[E].handle){o.event.remove(o.cache[E].handle.elem)}}});(function(){o.support={};var F=document.documentElement,G=document.createElement("script"),K=document.createElement("div"),J="script"+(new Date).getTime();K.style.display="none";K.innerHTML=' <link/><table></table><a href="/a" style="color:red;float:left;opacity:.5;">a</a><select><option>text</option></select><object><param/></object>';var H=K.getElementsByTagName("*"),E=K.getElementsByTagName("a")[0];if(!H||!H.length||!E){return}o.support={leadingWhitespace:K.firstChild.nodeType==3,tbody:!K.getElementsByTagName("tbody").length,objectAll:!!K.getElementsByTagName("object")[0].getElementsByTagName("*").length,htmlSerialize:!!K.getElementsByTagName("link").length,style:/red/.test(E.getAttribute("style")),hrefNormalized:E.getAttribute("href")==="/a",opacity:E.style.opacity==="0.5",cssFloat:!!E.style.cssFloat,scriptEval:false,noCloneEvent:true,boxModel:null};G.type="text/javascript";try{G.appendChild(document.createTextNode("window."+J+"=1;"))}catch(I){}F.insertBefore(G,F.firstChild);if(l[J]){o.support.scriptEval=true;delete l[J]}F.removeChild(G);if(K.attachEvent&&K.fireEvent){K.attachEvent("onclick",function(){o.support.noCloneEvent=false;K.detachEvent("onclick",arguments.callee)});K.cloneNode(true).fireEvent("onclick")}o(function(){var L=document.createElement("div");L.style.width=L.style.paddingLeft="1px";document.body.appendChild(L);o.boxModel=o.support.boxModel=L.offsetWidth===2;document.body.removeChild(L).style.display="none"})})();var w=o.support.cssFloat?"cssFloat":"styleFloat";o.props={"for":"htmlFor","class":"className","float":w,cssFloat:w,styleFloat:w,readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",tabindex:"tabIndex"};o.fn.extend({_load:o.fn.load,load:function(G,J,K){if(typeof G!=="string"){return this._load(G)}var I=G.indexOf(" ");if(I>=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("<div/>").append(M.responseText.replace(/<script(.|\s)*?\/script>/g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password|search/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();T.onload=T.onreadystatechange=null;H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H<F;H++){var E=o.data(this[H],"olddisplay");this[H].style.display=E||"";if(o.css(this[H],"display")==="none"){var G=this[H].tagName,K;if(m[G]){K=m[G]}else{var I=o("<"+G+" />").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}o.data(this[H],"olddisplay",K)}}for(var H=0,F=this.length;H<F;H++){this[H].style.display=o.data(this[H],"olddisplay")||""}return this}},hide:function(H,I){if(H){return this.animate(t("hide",3),H,I)}else{for(var G=0,F=this.length;G<F;G++){var E=o.data(this[G],"olddisplay");if(!E&&E!=="none"){o.data(this[G],"olddisplay",o.css(this[G],"display"))}}for(var G=0,F=this.length;G<F;G++){this[G].style.display="none"}return this}},_toggle:o.fn.toggle,toggle:function(G,F){var E=typeof G==="boolean";return o.isFunction(G)&&o.isFunction(F)?this._toggle.apply(this,arguments):G==null||E?this.each(function(){var H=E?G:o(this).is(":hidden");o(this)[H?"show":"hide"]()}):this.animate(t("toggle",3),G,F)},fadeTo:function(E,G,F){return this.animate({opacity:G},E,F)},animate:function(I,F,H,G){var E=o.speed(F,H,G);return this[E.queue===false?"each":"queue"](function(){var K=o.extend({},E),M,L=this.nodeType==1&&o(this).is(":hidden"),J=this;for(M in I){if(I[M]=="hide"&&L||I[M]=="show"&&!L){return K.complete.call(this)}if((M=="height"||M=="width")&&this.style){K.display=o.css(this,"display");K.overflow=this.style.overflow}}if(K.overflow!=null){this.style.overflow="hidden"}K.curAnim=o.extend({},I);o.each(I,function(O,S){var R=new o.fx(J,K,O);if(/toggle|show|hide/.test(S)){R[S=="toggle"?L?"show":"hide":S](I)}else{var Q=S.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),T=R.cur(true)||0;if(Q){var N=parseFloat(Q[2]),P=Q[3]||"px";if(P!="px"){J.style[O]=(N||1)+P;T=((N||1)/R.cur(true))*T;J.style[O]=T+P}if(Q[1]){N=((Q[1]=="-="?-1:1)*N)+T}R.custom(T,N,P)}else{R.custom(T,S,"")}}});return true})},stop:function(F,E){var G=o.timers;if(F){this.queue([])}this.each(function(){for(var H=G.length-1;H>=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)&&!n){n=setInterval(function(){var K=o.timers;for(var J=0;J<K.length;J++){if(!K[J]()){K.splice(J--,1)}}if(!K.length){clearInterval(n);n=g}},13)}},show:function(){this.options.orig[this.prop]=o.attr(this.elem.style,this.prop);this.options.show=true;this.custom(this.prop=="width"||this.prop=="height"?1:0,this.cur());o(this.elem).show()},hide:function(){this.options.orig[this.prop]=o.attr(this.elem.style,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(H){var G=e();if(H||G>=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" cellpadding="0" cellspacing="0"><tr><td></td></tr></table>';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(I,G){var E=I?"Left":"Top",H=I?"Right":"Bottom",F=G.toLowerCase();o.fn["inner"+G]=function(){return this[0]?o.css(this[0],F,false,"padding"):null};o.fn["outer"+G]=function(K){return this[0]?o.css(this[0],F,false,K?"margin":"border"):null};var J=G.toLowerCase();o.fn[J]=function(K){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+G]||document.body["client"+G]:this[0]==document?Math.max(document.documentElement["client"+G],document.body["scroll"+G],document.documentElement["scroll"+G],document.body["offset"+G],document.documentElement["offset"+G]):K===g?(this.length?o.css(this[0],J):null):this.css(J,typeof K==="string"?K:K+"px")}})})(); diff --git a/Database/CoolRunQuery/html/js/jquery.popupWindow.js b/Database/CoolRunQuery/html/js/jquery.popupWindow.js new file mode 100644 index 00000000000..3e25dd5f699 --- /dev/null +++ b/Database/CoolRunQuery/html/js/jquery.popupWindow.js @@ -0,0 +1,62 @@ +(function($){ + $.fn.popupWindow = function(instanceSettings){ + + return this.each(function(){ + + $(this).click(function(){ + + $.fn.popupWindow.defaultSettings = { + centerBrowser:0, // center window over browser window? {1 (YES) or 0 (NO)}. overrides top and left + centerScreen:0, // center window over entire screen? {1 (YES) or 0 (NO)}. overrides top and left + height:500, // sets the height in pixels of the window. + left:0, // left position when the window appears. + location:0, // determines whether the address bar is displayed {1 (YES) or 0 (NO)}. + menubar:0, // determines whether the menu bar is displayed {1 (YES) or 0 (NO)}. + resizable:0, // whether the window can be resized {1 (YES) or 0 (NO)}. Can also be overloaded using resizable. + scrollbars:0, // determines whether scrollbars appear on the window {1 (YES) or 0 (NO)}. + status:0, // whether a status line appears at the bottom of the window {1 (YES) or 0 (NO)}. + width:500, // sets the width in pixels of the window. + windowName:null, // name of window set from the name attribute of the element that invokes the click + windowURL:null, // url used for the popup + top:0, // top position when the window appears. + toolbar:0 // determines whether a toolbar (includes the forward and back buttons) is displayed {1 (YES) or 0 (NO)}. + }; + + settings = $.extend({}, $.fn.popupWindow.defaultSettings, instanceSettings || {}); + + var windowFeatures = 'height=' + settings.height + + ',width=' + settings.width + + ',toolbar=' + settings.toolbar + + ',scrollbars=' + settings.scrollbars + + ',status=' + settings.status + + ',resizable=' + settings.resizable + + ',location=' + settings.location + + ',menuBar=' + settings.menubar; + + settings.windowName = this.name || settings.windowName; + settings.windowURL = this.href || settings.windowURL; + var centeredY,centeredX; + + if(settings.centerBrowser){ + + if ($.browser.msie) {//hacked together for IE browsers + centeredY = (window.screenTop - 120) + ((((document.documentElement.clientHeight + 120)/2) - (settings.height/2))); + centeredX = window.screenLeft + ((((document.body.offsetWidth + 20)/2) - (settings.width/2))); + }else{ + centeredY = window.screenY + (((window.outerHeight/2) - (settings.height/2))); + centeredX = window.screenX + (((window.outerWidth/2) - (settings.width/2))); + } + window.open(settings.windowURL, settings.windowName, windowFeatures+',left=' + centeredX +',top=' + centeredY).focus(); + }else if(settings.centerScreen){ + centeredY = (screen.height - settings.height)/2; + centeredX = (screen.width - settings.width)/2; + window.open(settings.windowURL, settings.windowName, windowFeatures+',left=' + centeredX +',top=' + centeredY).focus(); + }else{ + window.open(settings.windowURL, settings.windowName, windowFeatures+',left=' + settings.left +',top=' + settings.top).focus(); + } + return false; + }); + + }); + }; +})(jQuery); diff --git a/Database/CoolRunQuery/html/js/rq.js b/Database/CoolRunQuery/html/js/rq.js new file mode 100644 index 00000000000..462bbd5d526 --- /dev/null +++ b/Database/CoolRunQuery/html/js/rq.js @@ -0,0 +1,48 @@ + +function toggle_dq(div) { + var hide = div.innerHTML.match(/hide/); + var res_table = document.getElementById('resulttable'); + var fields = res_table.getElementsByTagName('table'); + for (var i=0; i<fields.length; i++) { + if(fields[i].className != 'dqdefects') continue; + var lines = fields[i].getElementsByTagName('tr'); + for (var l=0; l<lines.length; l++) { + var classname = lines[l].className; + if(classname.match(/tolerable/)) { + if(hide) { + lines[l].style.visibility="collapse"; + } else { + lines[l].style.visibility="visible"; + } + } + } + } + div.innerHTML=hide?"[show tolerable]":"[hide tolerable]"; +} + +function switchUTC(span) { + if( span.className.match(/TZactive/) ) return; + span.className = "TZactive"; + var other = document.getElementById('TZlocal'); + other.className = "TZinactive"; + + for(var l=0; l<document.styleSheets.length; l++) { + if (document.styleSheets[l].cssRules) + crossrules=document.styleSheets[l].cssRules; + else if (document.styleSheets[l].rules) + crossrules=document.styleSheets[l].rules; + for(var r=0; r<crossrules.length; r++) { + if(crossrules[r].selectorText.match(/TZactive/)) { + //alert(crossrules[r].selectorText); + //crossrules[r].style.backgroundColor="green"; + } + } + } +} + +function switchLocal(span) { + if( span.className.match(/TZactive/) ) return; + span.className = "TZactive"; + var other = document.getElementById('TZutc'); + other.className = "TZinactive"; +} diff --git a/Database/CoolRunQuery/html/js/rq_examples.js b/Database/CoolRunQuery/html/js/rq_examples.js new file mode 100644 index 00000000000..80b2fff1a01 --- /dev/null +++ b/Database/CoolRunQuery/html/js/rq_examples.js @@ -0,0 +1,500 @@ + + +function insertExamples() { + var x = document.getElementById("exampleRunEvt"); + if(x==null) return; + insertExamplesRunEvt(); + insertExamplesTime(); + insertExamplesDetectors(); + insertExamplesStreams(); + insertExamplesMagnets(); + insertExamplesDQ(); + insertExamplesTrigger(); + insertExamplesPartition(); + insertExamplesDatasets(); + insertExamplesBeamSpot(); + insertExamplesLAr(); + insertExamplesLumi(); + insertExamplesLHC(); + insertExamplesOther(); +} + +var newtext = 'blank'; +function addColumn(newtext) { + frm = document.forms["RunQueryInput"].q; + frm.value = newtext; +} + + +function insertExamplesRunEvt() { + var x = document.getElementById("exampleRunEvt"); + x.innerHTML = '\ +<div class="Pad">\ + <table class="exampletable">\ + <tr>\ + <td class="pgm" onClick="addColumn("find run 90270-90350 and events 100000+ / show run and events");return false">find run 90270-90350 and events 100000+ / show run and events</td>\ + <td class="cmt"><font color="red">Select</font> runs in given <font color="red">run number range</font> and min. <font color="red">number of events</font>, <br/> and <font color="red">show</font> runs and number of events for selected runs.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 90270-90350 and ev 100k+ / sh r and ev");return false">f r 90270-90350 and ev 100k+ / sh r and ev</td>\ + <td class="cmt">Allowed <font color="red">abbreviations</font> − same query as above; <br/> note that the <i>show</i> part could be dropped since same as default.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 90270+ and ev 100k- ");return false">f r 90270+ and ev 100k- </td>\ + <td class="cmt">Select all runs with run number >= 90270 and < 100k events.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 90270,90275,90380+ and ev 100k-200k");return false">f r 90270,90275,90380 and ev 100k-200k</td>\ + <td class="cmt">Select any of the given run numbers if number of events in given range.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r last 10 / show all / nodef ");return false">f r last 10 / show all / nodef </td>\ + <td class="cmt">Select <font color="red">last 10 runs</font>; same in short: <font color="red">f r l 10 / sh all / nodef</font>.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r data10_7TeV.periodC / sh ready");return false">f r data10_7TeV.periodC / sh ready </td>\ + <td class="cmt">Select runs belonging to a given <font color="red">run period</font><br>(giving only "periodC" would use "data11_7TeV" as default)</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 2010.C / sh ready");return false">f r 2010.C / sh ready </td>\ + <td class="cmt">Select runs belonging to a given <font color="red">run period</font><br>(giving only "C" would use "2011" as default)</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r periodA-periodC,periodD / sh ready");return false">f r periodA-periodC,periodD / sh ready </td>\ + <td class="cmt">Select runs belonging to the given <font color="red">range of run periods</font> (only main letters a considered, so periodA-periodD, means use periods A,B,C,D, but not C1, C2, ...)</td>\ + </tr>\ + </table>\ +</div>'; +} + + +function insertExamplesTime() { + var x = document.getElementById("exampleTime"); + x.innerHTML = '\ +<div class="Pad">\ + <table class="exampletable">\ + <tr>\ + <td class="pgm" onClick="addColumn("find time last 24h / sh r and ev and t / utc");return false">find time last 24h / sh r and ev and t / utc</td>\ + <td class="cmt">Select runs that <font color="red">ended within the last 24h</font>; format examples: 5d, 1d = 24h, 5h, ... <br> extra option <font color="red">utc</font>, default is CEST (CERN local time)</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f t 10.9.2008-13.9.2008 / sh r and ev and t");return false">f t 10.9.2008-13.9.2008 / sh r and ev and t</td>\ + <td class="cmt">Select runs with start/end dates within given <font color="red">time period</font>; format: DD.MM.YYYY.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f t 13.9.2008+ / sh t");return false">f t 13.9.2008+ / sh t</td>\ + <td class="cmt">Select all runs including and <u>after</u> Sep 13, 2008.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f t 13.9.2008- / sh t");return false">f t 13.9.2008- / sh t</td>\ + <td class="cmt">Select all runs including and <u>before</u> Sep 13, 2008.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 89465-93000 and duration 2m+ / sh t and dur ");return false">f r 89465-93000 and duration 2m+ / sh t and dur </td>\ + <td class="cmt">Select runs that had a <font color="red">duration</font> of at least two minutes; <br/>\ + replace "m" by "h" and "s" for hours and seconds, respectively;<br>note: only integer numbers allowed! </td>\ + </tr>\ + </table>\ +</div>'; +} + + +function insertExamplesDetectors() { + var x = document.getElementById("exampleDetectors"); + x.innerHTML = '\ +<div class="Pad">\ + <table class="exampletable">\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 90270-90350 and det pix / sh r and ev and det");return false">f r 90270-90350 and det pix / sh r and ev and det</td>\ + <td class="cmt">Select runs with all Pixel in and show all <font color="red">participating detectors</font>.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 90100-90150 and det any sct / sh r and ev and det");return false">f r 90100-90150 and det any sct / sh r and ev and det</td>\ + <td class="cmt">Select runs with at least one SCT in and show all participating detectors.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 90270-90350 and det all / sh r and ev and det");return false">f r 90270-90350 and det all / sh r and ev and det</td>\ + <td class="cmt">Select runs with all detectors in; here <i>all</i> means the systems used during the 2008 combined data taking period, ie, not including for example CSC.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("<b><i>Decode detector mask:</i></b> ");return false"><b><i>Decode detector mask:</a></i></b> </td>\ + <td class="cmt"></td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("detmask 562937068519415");return false">detmask 562937068519415</td>\ + <td class="cmt">Interpret <font color="red">detector mask</font>.</td>\ + </tr>\ + </table>\ +</div>'; +} + + +function insertExamplesStreams() { + var x = document.getElementById("exampleStreams"); + x.innerHTML = '\ +<div class="Pad">\ + <table class="exampletable">\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 90270-90350 / sh r and ev and st");return false">f r 90270-90350 / sh r and ev and st</td>\ + <td class="cmt">Show <font color="red">all produced streams and events per stream</font> for selected runs.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("</td><td class="");return false"></td><td class="cmt">\ + By placing the <font color="red">mouse over</font> the number of events of a stream one can display the <font color="red">stream overlaps</font> − computed relative to the number of events in that stream. </td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 90270-90350 and st *IDCos* 10k+ and st *RPC* / sh st phy*,cal*");return false">f r 90270-90350 and st *IDCos* 10k+ and st *RPC* / sh st phy*,cal*</td>\ + <td class="cmt">Select runs where physics_IDCosmic stream has more than 10000 events and the RPC* stream is enabled, shows all physics and calibration streams. </td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 90749-90785 and st *RPC* and st !physics_L1CaloEM / sh st phy*");return false">f r 90749-90785 and st *RPC* and st !physics_L1CaloEM / sh st phy*</td>\ + <td class="cmt">Select runs where a RPC stream but <font color="red">no physics_L1CaloEM </font> stream is written.</td>\ + </table>\ +</div>'; +} + + + +function insertExamplesMagnets() { + var x = document.getElementById("exampleMagnets"); + x.innerHTML = '\ +<div class="Pad">\ + <table class="exampletable">\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 90270-90350 and mag s and not mag t / sh r and ev and mag");return false">f r 90270-90350 and mag s and not mag t / sh r and ev and mag</td>\ + <td class="cmt">Select runs with <font color="red">solenoid on and toroid off</font>.</td>\ + </tr>\ + </table>\ +</div>'; +} + + + + + +function insertExamplesDQ() { + var x = document.getElementById("exampleDQ"); + x.innerHTML = '\ +<div class="Pad">\ + <table class="exampletable">\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 2012.B6 / sh dqsum");return false">f r 2012.B6 / sh dqsum</td>\ + <td class="cmt">Show <font color="red">data quality</font> summary: defects, plots and luminosity losses per system (restricted to LBs with ATLAS READY).</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 2011.C / sh dq");return false">f r 2011.C / sh dq</td>\ + <td class="cmt">Show <font color="red">data quality</font> defects (restricted to LBs with ATLAS READY).</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r B / sh dq #DetStatus-v08-pro07");return false">f r B / sh dq #DetStatus-v08-pro07</td>\ + <td class="cmt">Show <font color="red">data quality </font> defects with COOL tag DetStatus-v08-pro07.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r A / sh r and dq det");return false">f r A / sh dq det</td>\ + <td class="cmt">Show <font color="red">detector defects</font> in period A of current year.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 2010.G1 / sh dq TRIG_");return false">f r 2010.G1 / sh dq TRIG_</td>\ + <td class="cmt">Show defects <font color="red">matching pattern</font>. Use "pattern$" for exact match of pattern</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r B1 and dq !CP_TAU #DetStatus-v08-pro07");return false">f r B1 and dq !CP_TAU #DetStatus-v08-pro07</td>\ + <td class="cmt">Select all lumiblocks in B1 that have <font color="red">no CP_TAU defect</font>. The specified defect has to match exactly, no wild cards.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 90270-90350 / sh r and dq pix,sct DQMFOFL");return false">f r 90270-90350 / sh r and dq pix,sct DQMFOFL</td>\ + <td class="cmt">Same as above for <font color="red">"DQMFOFL" folder</font>; \ + choices are: "SHIFTOFL", "SHIFTONL", "DQCALCOFL", "DQMFOFL", "DQMFONL", "DQMFONLLB", "DCSOFL", "TISUMM", "LBSUMM" [default: "SHIFTOFL"].</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 135195 / sh r and dq DQMFOFL#DetStatusDQMFOFL-express-pass1");return false">f r 135195 / sh r and dq DQMFOFL#DetStatusDQMFOFL-express-pass1</td>\ + <td class="cmt">Specification of a <font color="red">DB tag</font>\ + [default: "HEAD"].</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 90270-90350 and dq em y+ and dq pixb y+ / sh dq pix,sct,em,til");return false">f r 90270-90350 and dq em y+ and dq pixb y+ / sh dq pix,sct,em,til</td>\ + <td class="cmt">Select runs where LAr-EM and Tile DQ flags are at least "Y" and Pixel-B is "G". </td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 90270-91350 and dq lar g / sh dq lar");return false">f r 90270-91350 and dq lar g / sh dq lar</td>\ + <td class="cmt">Select runs where all LAr-EM are "green". </td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 90270-91350 and dq any lar g / sh dq lar");return false">f r 90270-91350 and dq any lar g / sh dq lar</td>\ + <td class="cmt">Select runs where at least one LAr-EM are "green". </td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 90270-90350 and dq any pix n.a. / sh dq pix");return false">f r 90270-90350 and dq any pix n.a. / sh dq pix</td>\ + <td class="cmt">Select runs where at least one Pix qualitity flag is unset. </td>\ + </tr>\ + </table>\ +</div>\ +'; +} + + +function insertExamplesProject() { + var x = document.getElementById("exampleProject"); + x.innerHTML = '\ +<div class="Pad">\ + <table class="exampletable">\ + </table>\ +</div>\ +'; +} + +function insertExamplesTrigger() { + var x = document.getElementById("exampleTrigger"); + x.innerHTML = '\ +<div class="Pad">\ + <table class="exampletable">\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 90270-90350 and ev 100k+ / sh allev");return false">f r 90270-90350 and ev 100k+ / sh allev</td>\ + <td class="cmt">Show <font color="red">all event numbers, i.e., at EF, L2, L1 and SFO (inclusive)</font> levels.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r last 10 / sh trigkeys and rel");return false">f r last 10 / sh trigkeys and rel</td>\ + <td class="cmt">Show <font color="red">all trigger keys and HLT release</font> for selected runs.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r last 10 / sh trigrates L1_MBTS_*");return false">f r last 10 / sh trigrates L1_MBTS_*</td>\ + <td class="cmt">Show <font color="red">trigger rates</font> (max 50). Currently only Level 1.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 91890-92070 and smk 368,373 / sh trigkeys");return false">f r 91890-92070 and smk 368,373 / sh trigkeys</td>\ + <td class="cmt">Select runs with <font color="red">trigger "super-master" key 368 or 373</font>, show keys and <font color="red">release</font>.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 91890-92070 / sh tr L2_E*,L2_Cosmic* ");return false">f r 91890-92070 / sh tr L2_E*,L2_Cosmic* </td>\ + <td class="cmt">Show <font color="red">show triggers matching patterns L2_E* or L2_Cosmic*</font> [not case sensitive].</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 91890-92070 and tr EF_e5* ");return false">f r 91890-92070 and tr EF_e5* </td>\ + <td class="cmt">Show <font color="red">runs with triggers matching pattern EF_e5*</font> [not case sensitive].</td>\ + </tr>\ + </table>\ +</div>\ +'; +} + + +function insertExamplesPartition() { + var x = document.getElementById("examplePartition"); + x.innerHTML = '\ +<div class="Pad">\ + <table class="exampletable">\ + <tr>\ + <td class="pgm" onClick="addColumn("f t 10.9.2008-13.9.2008 and part ATLAS / sh t and part");return false">f t 10.9.2008-13.9.2008 and part ATLAS / sh t and part</td>\ + <td class="cmt">Select runs with <font color="red">DAQ partition</font> "ATLAS".</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r l 10 / sh ready");return false">f r l 10 / sh ready</td>\ + <td class="cmt">Show (or similar: select) <font color="red">ReadyForPhysics flag</font> for runs.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f t 10.9.2008-13.9.2008 and ptag data08_1beam* / sh t and ptag");return false">f t 10.9.2008-13.9.2008 and ptag data08_1beam* / sh t and ptag</td>\ + <td class="cmt">Select runs with single-beam <font color="red">project tag</font> (note that the project tag is denoted "filenameTag" in COOL).</td>\ + </tr>\ + <tr>\ + <td colspan="2">\ + <font size="-1">\ + <table style="border-collapse: collapse;">\ + <tr style="background-color: #a5a2a2;"><td style="padding: 0px; width: 120px;"><b>Project Tag</b></td><td style="padding: 0px;"><b>Definition</b></td></tr>\ + <tr><td style="padding: 0px;">dataYY_900GeV</td><td style="padding: 0px;">900 GeV collisions taken during the year 20YY</td></tr>\ + <tr><td style="padding: 0px;">dataYY_2TeV</td><td style="padding: 0px;">Collisions at 2.X TeV</td></tr>\ + <tr><td style="padding: 0px;">dataYY_7TeV</td><td style="padding: 0px;">Collisions at 7.X TeV</td></tr>\ + <tr><td style="padding: 0px;">dataYY_1beam</td><td style="padding: 0px;">Combined data taking with single beam (or 2 non-colliding beams)</td></tr>\ + <tr><td style="padding: 0px;">dataYY_cos</td><td style="padding: 0px;">Combined cosmic data taking</td></tr>\ + </table>\ + </font>\ + </td>\ + </tr>\ + </table>\ +</div>\ +'; +} + +function insertExamplesDatasets() { + var x = document.getElementById("exampleDatasets"); + x.innerHTML = '\ +<div class="Pad">\ + <table class="exampletable">\ + <tr>\ + <td class="pgm" onClick="addColumn("f run last 10 / sh time and datasets");return false">f run last 10 / sh time and datasets</td>\ + <td class="cmt">Select last 10 runs and show <font color="red">all datasets produced in Tier-0.</font></td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r l 10 / sh t and da");return false">f r l 10 / sh t and da</td>\ + <td class="cmt">As above <font color="red">in short.</font></td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r l 10 / sh t and da caf");return false">f r l 10 / sh t and da caf</td>\ + <td class="cmt">Also checks if files are <font color="red">available on CAF pool (ATLCAL)</font> (slow query). <br>Note: <b>recent replications (< 24h) may not be included.</b></td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r l 10 / sh t and da *ntup*,*TAG*");return false">f r l 10 / sh t and da *ntup*,*TAG*</td>\ + <td class="cmt">As above but selecting <font color="red">dataset name patterns</font> - selection is case insensitive.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r l 10 / sh t and da *ntup*,*TAG* caf");return false">f r l 10 / sh t and da *ntup*,*TAG* caf</td>\ + <td class="cmt">As above but including CAF pool availability information.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f run last 10 / sh time and str");return false">f run last 10 / sh time and str</td>\ + <td class="cmt">Select last 10 runs and show streams: move mouse over stream info to obtain information on the <font color="red">data types produced in the reconstruction and merging steps of the Tier-0, and their CAF replication status</font>.</td>\ + </tr>\ + </table>\ +</div>\ +'; +} + +function insertExamplesBeamSpot() { + var x = document.getElementById("exampleBeamSpot"); + x.innerHTML = '\ +<div class="Pad">\ + <table class="exampletable">\ + <tr>\ + <td class="pgm" onClick="addColumn("find run last 10 / show bs ");return false">find run last 10 / show bs </td>\ + <td class="cmt">Show <font color="red">offline beam spot parameters</font> for selected runs.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("find run last 10 / show bs <COOL-tag>");return false">find run last 10 / show bs <COOL-tag></td>\ + <td class="cmt">Same as above but assign which <font color="red">COOL tag</font> to use;<br>default tag: "IndetBeampos-ES1-UPD2".</td>\ + <tr>\ + <td class="pgm" onClick="addColumn("find run last 10 / show bs online");return false">find run last 10 / show bs online </td>\ + <td class="cmt">Show <font color="red">online beam spot parameters</font> for selected runs.</td>\ + </tr>\ + </tr>\ + </table>\ +</div>\ +'; +} + + +function insertExamplesLAr() { + var x = document.getElementById("exampleLAr"); + x.innerHTML = '\ +<div class="Pad">\ + <table class="exampletable">\ + <tr>\ + <td class="pgm" onClick="addColumn("find run last 10 / show larcond ");return false">find run last 10 / show larcond </td>\ + <td class="cmt">Show <font color="red">LAr conditions</font> for selected runs.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r l 10 and larc nsamples 7 and larc runtype 1 / sh larc ");return false">f r l 10 and larc nsamples 7 and larc runtype 1 / sh larc </td>\ + <td class="cmt">As above, in short, and selecting on <font color="red">number of samples and run tupe</font>.</td>\ + </tr>\ + </table>\ +</div>\ +'; +} + +function insertExamplesLumi() { + var x = document.getElementById("exampleLumi"); + x.innerHTML = '\ +<div class="Pad">\ + <table class="exampletable">\ + <tr>\ + <td class="pgm" onClick="addColumn("find run 152166 / show lhc ");return false">find run 152166 / show lhc </td>\ + <td class="cmt">Show LHC information and <font color="red">online luminosity</font> for selected runs.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("find run 152166-152508 and lumi 20ub+ / show lhc ");return false">find run 152166-152508 and lumi 20ub+ / show lhc </td>\ + <td class="cmt">Find runs within given range with <font color="red">integrated luminosity (online) larger than 20 inverse microbarns</font> (the "+" ("−") sign indicates "more than" ("less than")).<br>Allowed units are: b, mb, ub, nb, pb, fb.<br>Note that only luminosity with <b>stable beams</b> is considered (hence: the requirement explicitly selects runs with stable beams).</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("find run 142193 / show lumi ");return false">find run 142193 / show lumi </td>\ + <td class="cmt">Show <font color="red">offline luminosity</font> for selected runs.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("find run 142193 / show lumi 0 OflLumi-Dec2009-001");return false">find run 142193 / show lumi 0 OflLumi-Dec2009-001</td>\ + <td class="cmt">Show offline luminosity for <font color="red">channel 0 and COOL tag OflLumi-Dec2009-001</font>;<br> default channel and tag: 0, OflLumi-Dec2009-001</td>\ + </tr>\ + </table>\ +</div>\ +'; +} + +function insertExamplesLHC() { + var x = document.getElementById("exampleLHC"); + x.innerHTML = '\ +<div class="Pad">\ + <table class="exampletable">\ + <tr>\ + <td class="pgm" onClick="addColumn("find run last 10 / show lhc ");return false">find run last 10 / show lhc </td>\ + <td class="cmt">Show <font color="red">LHC information (stable beams, beam energy and intensities, online luminosity)</font> for selected runs.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("find run last 10 / show lhc all ");return false">find run last 10 / show lhc all</td>\ + <td class="cmt">Show also: <font color="red">beam mode and buch pattern (BCID mask)</font>.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("find run last 20 and lhc stablebeams true / show lhc");return false">find run last 20 and lhc stablebeams true / show lhc</td>\ + <td class="cmt">Select runs with <font color="red">at least one LB with stable-beams</font> (can replace "true" by "t").</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r l 10 and lhc beamenergy 3400+ / sh t and lhc");return false">f r l 10 and lhc beamenergy 3400+ / sh lhc</td>\ + <td class="cmt">As above, in short, and selecting on <font color="red">the beam energy</font>.<br>Numbers can be replaced by ranges, eg: 3400+, 3700-, 3400-3700.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r 152772-152876 and lhc fillnumber 1031-1033 / sh lhc");return false">f r 152772-152876 and lhc fillnumber 1031-1033 / sh lhc</td>\ + <td class="cmt">Select runs according to <font color="red">LHC fill number</font>.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("find run last 10 / show bpm ");return false">find run last 10 / show bpm </td>\ + <td class="cmt">Show <font color="red">beam positions measured by Beam Position Monitors (BPM)</font> for selected runs (the closest BPMs are located +-21m from the ATLAS IP).</td>\ + </tr>\ + </table>\ +</div>\ +'; +} + +function insertExamplesOther() { + var x = document.getElementById("exampleOther"); + x.innerHTML = '\ +<div class="Pad">\ + <table class="exampletable">\ + <tr>\ + <td><b><i>Summary pages:</i></b></td>\ + <td></td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r A-B / show summary ");return false">f r A-B / show summary </td>\ + <td class="cmt">Provide <font color="red">summary tables and plots</font> for selected runs.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r A-B / show dqsummary ");return false">f r A-B / show dqsummary </td>\ + <td class="cmt">Provide <font color="red">summary data quality tables</font> for selected runs.</td>\ + </tr>\ + <tr>\ + <td><b><i>Extra options</i></b> (3rd parameter):</td>\ + <td></td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r G / show r and ev / nodef ");return false">f r F / show r and ev / nodef </td>\ + <td class="cmt">Select runs with <font color="red">without default conditions</font>.</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r G / show r and ev / grl grl.xml:runlist ");return false">f r G / show r and ev / grl grl.xml:runlist </td>\ + <td class="cmt">Produce <font color="red">GRL</font> with optionally give file and runlist name (they defaults to \'MyLBCollection.xml:MyLBCollection\').</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("f r G / show r and ev / root ");return false">f r G / show r and ev / root </td>\ + <td class="cmt">Produce root files and overview histograms.</td>\ + </tr>\ + <td><b><i>Pileup:</i></b></td>\ + <td></td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("pileup 1.5e28 55 2 100000");return false">pileup 1.5e28 55 2 100000</td>\ + <td class="cmt">Compute <font color="red">pileup expectation</font>. The arguments are (in this order): <b>instantaneous luminosity</b>, <b>cross section</b> (unit: mb), <b>number of colliding bunches</b>, [<b>total number of triggered events in sample</b> (optional), default: 1]</td>\ + </tr>\ + <tr>\ + <td class="pgm" onClick="addColumn("pileup 1e10 17 55 2 1");return false">pileup 1e10 17 55 2 1</td>\ + <td class="cmt">Compute <font color="red">pileup expectation</font>. The arguments are (in this order): <b>beam intensity</b> (number of protons), <b>transverse beam size</b> (microns), <b>cross section</b> (mb), <b>number of colliding bunches</b>, [<b>total number of triggered events in sample</b> (optional), default: 1]</td>\ + </tr>\ + <tr>\ + </table>\ +</div>\ +'; +} diff --git a/Database/CoolRunQuery/html/js/rq_simplepopups.js b/Database/CoolRunQuery/html/js/rq_simplepopups.js new file mode 100644 index 00000000000..ba8c2cd156e --- /dev/null +++ b/Database/CoolRunQuery/html/js/rq_simplepopups.js @@ -0,0 +1,75 @@ +function helppopup() { + helpwin = window.open("atlas-runquery-help.html", "Help for Run Query","location=no,width=950,height=700,scrollbars=yes"); +} + +function openWindow(name,text) { + win = window.open("", name, "location=0,left=50,width=720,height=550,scrollbars=1"); + win.locationbar.visible = false; + win.focus(); + win.document.open(); + win.document.writeln( text ); + win.document.close(); +} + +function openTextWindow(name,text) { + textwin = window.open("", name, "location=0,width=720,height=750,scrollbars=1"); + textwin.locationbar.visible = false; + textwin.focus(); + textwin.document.open(); + textwin.document.writeln( text ); + textwin.document.close(); +} + +function openLumiWindow(name,text) { + lumiwin = window.open("", name, "location=0,width=950,height=310,scrollbars=1,status=0,toolbar=0"); + lumiwin.locationbar.visible = false; + lumiwin.focus(); + lumiwin.document.open(); + lumiwin.document.writeln( text ); + lumiwin.document.close(); +} + +function openNemoWindow(name,text) { + lumiwin = window.open("", name, "location=0,width=850,height=710,scrollbars=1,status=0,toolbar=0"); + lumiwin.locationbar.visible = false; + lumiwin.focus(); + lumiwin.document.open(); + lumiwin.document.writeln( text ); + lumiwin.document.close(); +} + +function openLargeWindow(name,text) { + largewin = window.open("", name, "location=0,width=830,height=600,scrollbars=1"); + largewin.locationbar.visible = false; + largewin.focus(); + largewin.document.open(); + largewin.document.writeln( text ); + largewin.document.close(); +} + +function openPageWindow(link,name) { + pagewin = window.open(link, name, "location=0,width=900,height=650,scrollbars=1"); +} + + +function relpopup() { + relwin = window.open("atlas-runquery-relnotes.html", "Change logs","location=no,width=645,height=500,scrollbars=yes"); +} + +function ShowMessage() { + if ((waitDiv=document.getElementById("wait"))!=null) { + if (waitDiv.style){ + document.getElementById('wait').style.visibility = 'visible' + } + } +} + +var win=null; +function NewWindow(mypage,myname,w,h,scroll,pos){ + if(pos=="random"){LeftPosition=(screen.width)?Math.floor(Math.random()*(screen.width-w)):100;TopPosition=(screen.height)?Math.floor(Math.random()*((screen.height-h)-75)):100;} + if(pos=="center"){LeftPosition=(screen.width)?(screen.width-w)/2:100;TopPosition=(screen.height)?(screen.height-h)/2:100;} + else if((pos!="center" && pos!="random") || pos==null){LeftPosition=0;TopPosition=20} + settings='width='+w+',height='+h+',top='+TopPosition+',left='+LeftPosition+',scrollbars='+scroll+',locationbar=no,directories=no,status=no,menubar=no,toolbar=no,resizable=no'; + win=window.open(mypage,myname,settings); +} + diff --git a/Database/CoolRunQuery/html/js/switcher.js b/Database/CoolRunQuery/html/js/switcher.js new file mode 100644 index 00000000000..c027fb5343f --- /dev/null +++ b/Database/CoolRunQuery/html/js/switcher.js @@ -0,0 +1,97 @@ + +function switcher() { + this.runs={}; + this.headers=[]; + this.imagesrc=['images/plus.gif','images/minus.gif']; +} + +switcher.prototype.init=function() { + var instanceOf=this; + this.collectElementbyClass("run") + for (var i=0; i<this.headers.length; i++) { + var header = this.headers[i] + var id = header.id; + this.runs[id]=new switchlb(header); + this.runs[id].imagesrc=this.imagesrc; + this.runs[id].init(); + } + //document.getElementById("expand-all").onclick=function(){instanceOf.toggleAll("doopen")}; + //document.getElementById("collapse-all").onclick=function(){instanceOf.toggleAll("doclose")}; + //alert("Test"); +}; + +switcher.prototype.collectElementbyClass=function(classname) { + var classnameRE=new RegExp("(^|\\s+)"+classname+"($|\\s+)", "i"); + var allelements = document.getElementsByTagName("tr"); + for (var i=0; i<allelements.length; i++) { + var lineEl = allelements[i]; + //if (lineEl.id!="" && typeof lineEl.className=="string" && lineEl.className.search(classnameRE)!=-1) + if (lineEl.id!="" && typeof lineEl.className=="string" && lineEl.className.match("^"+classname)) + this.headers.push(lineEl); + } +}; + +switcher.prototype.toggleAll=function(action) { + for (var i=0; i<this.headers.length; i++) { + var id = this.headers[i].id + this.runs[id].toggle(action); + } +} + +function switchlb(header) { + this.header = header; + this.pmelem = this.header.getElementsByTagName("td")[0]; + this.status = "open"; + this.imagesrc=['plus.gif','minus.gif']; +}; + +switchlb.prototype.init=function() { + var instanceOf=this; + this.toggle("doclose") + this.pmelem.onclick=function(){instanceOf.toggle()} +}; + +switchlb.prototype.toggle=function(action){ + this.collectLbByClassName(this.header.id); + if(action==undefined) { + if (this.status=="open") { + this.contractcontent(); + } else { + this.expandcontent(); + } + } else if(action=="doclose") { + this.contractcontent(); + } else if(action=="doopen") { + this.expandcontent(); + } +}; + +switchlb.prototype.contractcontent=function(){ + for(var i=0; i<this.lbs.length; i++) { + var innercontent=this.lbs[i]; + innercontent.style.visibility="collapse"; + } + this.pmelem.innerHTML='<img src="'+this.imagesrc[0]+'"/>' + this.status="closed"; +}; + +switchlb.prototype.expandcontent=function(){ + for(var i=0; i<this.lbs.length; i++) { + var innercontent=this.lbs[i]; + innercontent.style.visibility="visible"; + } + this.pmelem.innerHTML='<img src="'+this.imagesrc[1]+'"/>' + this.status="open"; +}; + +switchlb.prototype.collectLbByClassName=function(classname) { + var classnameRE=new RegExp("(^|\\s+)"+classname+"($|\\s+)", "i") //regular expression to screen for classname within element + var allLb = document.getElementsByTagName("tr") + this.lbs=[] + for (var i=0; i<allLb.length; i++) { + var lineEl = allLb[i] + if (typeof lineEl.className=="string" && lineEl.className.match("^lb-"+classname+"-")) { + this.lbs.push(lineEl); + } + } +} diff --git a/Database/CoolRunQuery/html/js/tabView.js b/Database/CoolRunQuery/html/js/tabView.js new file mode 100644 index 00000000000..4d45fdc7ce1 --- /dev/null +++ b/Database/CoolRunQuery/html/js/tabView.js @@ -0,0 +1,84 @@ +/* Copyright (C) 2005 Ilya S. Lyubinskiy. All rights reserved. +Technical support: http://www.php-development.ru/ + +YOU MAY NOT +(1) Remove or modify this copyright notice. +(2) Distribute this code, any part or any modified version of it. + Instead, you can link to the homepage of this code: + http://www.php-development.ru/javascripts/tabview.php. + +YOU MAY +(1) Use this code on your website. +(2) Use this code as a part of another product. + +NO WARRANTY +This code is provided "as is" without warranty of any kind, either +expressed or implied, including, but not limited to, the implied warranties +of merchantability and fitness for a particular purpose. You expressly +acknowledge and agree that use of this code is at your own risk. + + +If you find my script useful, you can support my site in the following ways: +1. Vote for the script at HotScripts.com (you can do it on my site) +2. Link to the homepage of this script or to the homepage of my site: + http://www.php-development.ru/javascripts/tabview.php + http://www.php-development.ru/ + You will get 50% commission on all orders made by your referrals. + More information can be found here: + http://www.php-development.ru/affiliates.php +*/ + +// ----- Auxiliary ------------------------------------------------------------- + +function tabview_aux(TabViewId, id) +{ + var TabView = document.getElementById(TabViewId); + + if(TabView==null) return; + + // ----- Tabs ----- + + var Tabs = TabView.firstChild; + while (Tabs.className != "Tabs" ) Tabs = Tabs.nextSibling; + + var Tab = Tabs.firstChild; + var i = 0; + + do + { + if (Tab.tagName == "A") + { + i++; + Tab.href = "javascript:tabview_switch('"+TabViewId+"', "+i+");"; + Tab.className = (i == id) ? "Active" : ""; + Tab.blur(); + } + } + while (Tab = Tab.nextSibling); + + // ----- Pages ----- + + var Pages = TabView.firstChild; + while (Pages.className != 'Pages') Pages = Pages.nextSibling; + + var Page = Pages.firstChild; + var i = 0; + + do + { + if (Page.className == 'Page') + { + i++; + if (Pages.offsetHeight) Page.style.height = (Pages.offsetHeight-2)+"px"; + Page.style.overflow = "auto"; + Page.style.display = (i == id) ? 'block' : 'none'; + } + } + while (Page = Page.nextSibling); +} + +// ----- Functions ------------------------------------------------------------- + +function tabview_switch(TabViewId, id) { tabview_aux(TabViewId, id); } + +function tabview_initialize(TabViewId) { tabview_aux(TabViewId, 1); } diff --git a/Database/CoolRunQuery/macros/AtlRunQueryGraphs.C b/Database/CoolRunQuery/macros/AtlRunQueryGraphs.C new file mode 100644 index 00000000000..2298acdeb10 --- /dev/null +++ b/Database/CoolRunQuery/macros/AtlRunQueryGraphs.C @@ -0,0 +1,4 @@ +/* + Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration +*/ + diff --git a/Database/CoolRunQuery/macros/colours.C b/Database/CoolRunQuery/macros/colours.C new file mode 100644 index 00000000000..ddc496c5dfa --- /dev/null +++ b/Database/CoolRunQuery/macros/colours.C @@ -0,0 +1,68 @@ +/* + Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration +*/ + +// --- user-defined colors +#include "TROOT.h" +#include "TColor.h" + +#ifndef COLOURS__ +#define COLOURS__ + +static Int_t c_HiggsGreen = TColor::GetColor( "#cfff47" ); +static Int_t c_HiggsBlue = TColor::GetColor( "#A2DAFF" ); +static Int_t c_White = TColor::GetColor( "#ffffff" ); +static Int_t c_LightYellow = TColor::GetColor( "#ffff00" ); +static Int_t c_VLightYellow = TColor::GetColor( "#ffffe0" ); +static Int_t c_DarkYellow = TColor::GetColor( "#ffd700" ); +static Int_t c_VDarkYellow = TColor::GetColor( "#ffa500" ); +static Int_t c_LightOrange = TColor::GetColor( "#ffcc00" ); +static Int_t c_VVLightOrange = TColor::GetColor( "#ffee66" ); +static Int_t c_VLightOrange = TColor::GetColor( "#ffdd44" ); +static Int_t c_DarkOrange = TColor::GetColor( "#ff6600" ); +static Int_t c_VDarkOrange = TColor::GetColor( "#aa4400" ); +static Int_t c_LightGreen = TColor::GetColor( "#aaff33" ); +static Int_t c_VVLightGreen = TColor::GetColor( "#cdff88" ); +static Int_t c_VLightGreen = TColor::GetColor( "#bdff66" ); +static Int_t c_DarkGreen = TColor::GetColor( "#116600" ); +static Int_t c_VDarkGreen = TColor::GetColor( "#114400" ); +static Int_t c_NiceGreen = TColor::GetColor( "#4CC417" ); +static Int_t c_LightBlue = TColor::GetColor( "#66aaff" ); +static Int_t c_VVLightBlue = TColor::GetColor( "#aaddff" ); +static Int_t c_VLightBlue = TColor::GetColor( "#99bbff" ); +static Int_t c_Blue = TColor::GetColor( "#0000ff" ); +static Int_t c_DarkBlue = TColor::GetColor( "#0000bb" ); +static Int_t c_LightRed = TColor::GetColor( "#ff3333" ); +static Int_t c_DarkRed = TColor::GetColor( "#aa0000" ); +static Int_t c_NovelRed = TColor::GetColor( "#dd0033" ); + +static Int_t c_SteelBlue1 = TColor::GetColor( "#ededff" ); +static Int_t c_SteelBlue2 = TColor::GetColor( "#ddffdd" ); +static Int_t c_Violet = TColor::GetColor( "#aa11aa" ); + +static Int_t c_BlueT1 = TColor::GetColor( "#2255cc" ); +static Int_t c_BlueT2 = TColor::GetColor( "#4488dd" ); +static Int_t c_BlueT3 = TColor::GetColor( "#99bbff" ); +static Int_t c_BlueT4 = TColor::GetColor( "#99bbff" ); +static Int_t c_BlueT5 = TColor::GetColor( "#aaddff" ); +static Int_t c_NovelBlue = TColor::GetColor( "#2244a5" ); + +static Int_t c_DarkBlueT1 = TColor::GetColor( "#0000aa" ); +static Int_t c_DarkBlueT2 = TColor::GetColor( "#1122bb" ); +static Int_t c_DarkBlueT3 = TColor::GetColor( "#2233ee" ); +static Int_t c_DarkBlueT4 = TColor::GetColor( "#0022aa" ); +static Int_t c_DarkBlueT5 = TColor::GetColor( "#001177" ); + +static Int_t c_VLightGray = TColor::GetColor( "#eeeeee" ); +static Int_t c_LightGray = TColor::GetColor( "#cfcfcf" ); +static Int_t c_Gray = TColor::GetColor( "#888888" ); +static Int_t c_VDarkGray = TColor::GetColor( "#333333" ); +static Int_t c_DarkGray = TColor::GetColor( "#555555" ); +static Int_t c_Black = TColor::GetColor( "#000000" ); + +static Int_t c_ExclusionCol = TColor::GetColor( "#999999" ); +static Int_t c_ExclusionColL = TColor::GetColor( "#cccccc" ); + +static Int_t c_VLightBkg = TColor::GetColor( "#eeeeee" ); + +#endif diff --git a/Database/CoolRunQuery/macros/style.C b/Database/CoolRunQuery/macros/style.C new file mode 100644 index 00000000000..90c739ea58a --- /dev/null +++ b/Database/CoolRunQuery/macros/style.C @@ -0,0 +1,39 @@ +/* + Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration +*/ + +#include "TH1.h" +#include "TStyle.h" +#include "/Users/andreashoecker/ROOT/colours.C" + +#ifndef STYLE__ +#define STYLE__ + +// set frame styles +void SetFrameStyle( TH1* frame, Float_t scale = 1.0 ) +{ + frame->SetLabelOffset( 0.012, "X" );// label offset on x axis + frame->SetLabelOffset( 0.012, "Y" );// label offset on x axis + frame->GetXaxis()->SetTitleOffset( 1.25 ); + frame->GetYaxis()->SetTitleOffset( 0.94 ); + frame->GetXaxis()->SetTitleSize( 0.045*scale ); + frame->GetYaxis()->SetTitleSize( 0.045*scale ); + Float_t labelSize = 0.04*scale; + frame->GetXaxis()->SetLabelSize( labelSize ); + frame->GetYaxis()->SetLabelSize( labelSize ); + + // global style settings + gPad->SetTicks(); + gPad->SetBottomMargin( 0.120*scale ); + + // some style elements + gStyle->SetTitleFillColor( c_VLightGray ); + gStyle->SetTitleTextColor( c_VDarkGray ); + gStyle->SetTitleBorderSize( 1 ); + gStyle->SetTitleH( 0.05 ); + gStyle->SetTitleX( gPad->GetLeftMargin() ); + gStyle->SetTitleY( 1 - gPad->GetTopMargin() + gStyle->GetTitleH() ); + gStyle->SetTitleW( 1 - gPad->GetLeftMargin() - gPad->GetRightMargin() ); +} + +#endif diff --git a/Database/CoolRunQuery/python/AtlRunQueryAMI.py b/Database/CoolRunQuery/python/AtlRunQueryAMI.py new file mode 100644 index 00000000000..9a834651e44 --- /dev/null +++ b/Database/CoolRunQuery/python/AtlRunQueryAMI.py @@ -0,0 +1,231 @@ +#!/bin/env python + +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration +# +# ---------------------------------------------------------------- +# Script : AtlRunQueryAMI.py +# Project: AtlRunQuery +# Purpose: Utility to retrieve information from AMI +# Authors: Andreas Hoecker (CERN), Joerg Stelzer (DESY) +# Created: Nov 10, 2009 +# ---------------------------------------------------------------- +# + +import re + +class ARQ_AMI: + + _amiclient = None + store = {} + all_periods = None + + + @classmethod + def getAmiClient(cls, amiconf = None, verbose = False): + """get ami client + param amiconf: name of file with AMI user and pw + + If a valid filename is specified, it tries to read the + username and password from there. If that does not succeed or + no filename is specified, voms based authentication is tried. + """ + from pyAMI.pyAMI import AMI,pyAMIEndPoint + from os import stat,path + + useConfigFile = False + if amiconf: + if not path.exists(amiconf): + if verbose: + print "WARNING: AMI config file", amiconf, "does not exist. Need to rely on valid voms proxy." + elif stat(amiconf).st_mode & path.stat.S_IRUSR == 0: + if verbose: + print "WARNING: AMI config file", amiconf, "exists but is not readable. Need to rely on valid voms proxy." + else: + useConfigFile = True + + if useConfigFile: + pyAMIEndPoint.setType("replica") + ami=AMI() + ami.readConfig(amiconf) + if ami.checkAuth(): + print "... connecting to CERN AMI replica with user+pw" + return ami + pyAMIEndPoint.setType("main") + ami.certAuth() + if ami.checkAuth(): + print "... connecting to AMI main server with user+pw" + return ami + + print "WARNING: Authentication in config file",amiconf,"not valid, check format, user, pw. Need to rely on valid voms proxy." + + + pyAMIEndPoint.setType("replica") + ami=AMI() + if ami.checkAuth(): + print "... connecting to CERN replica using voms-proxy" + return ami + + + pyAMIEndPoint.setType("main") + ami.certAuth() + if ami.checkAuth(): + print "... connecting to main server using voms-proxy" + return ami + + + if verbose: + print "WARNING voms-proxy authentication not valid. No access to AMI." + return None + + + @classmethod + def amiclient(cls): + if cls._amiclient == None: + from os import getenv + from utils.AtlRunQueryUtils import runsOnServer + if runsOnServer(): + home = "/data/atrqadm/data/" + else: + home = getenv('HOME') + if not home: home = '~' + conffilename = home + "/private/AMIConf.txt" + cls._amiclient = cls.getAmiClient(conffilename) + if cls._amiclient==None: + print "ERROR: voms-proxy authentication not valid and no AMI configuration file",conffilename,"supplied. No access to AMI!" + cls._amiclient="No AMI" + return cls._amiclient + + @classmethod + def OpenAMIConnection(cls): + try: + from pyAMI.pyAMI import AMI,pyAMIEndPoint + #pyAMIEndPoint.setType("replica") + amiclient = AMI() + amiclient.readConfig("./AMIConf.txt") + return amiclient + except ImportError: + print 'ERROR: could not load pyAMI' + return None + + + @classmethod + def amiexec(cls, cmdList): + try: + result = cls.amiclient().execute(cmdList) + return result.getDict() + except Exception as ex: + print "AMI exception '",type(ex),"' occured" + print ex + return {} + + + @classmethod + def get_periods_for_run(cls, run): + try: run = int(run) + except: return [] + if not run in cls.store: + try: + print 'GetDataPeriodsForRun', '-runNumber=%i' % run + #result = cls.amiclient().execute(['GetDataPeriodsForRun', '-runNumber=%i' % run]) + result = cls.amiexec(['GetDataPeriodsForRun', '-runNumber=%i' % run]) + #cls.store[run] = sorted([ (int(e['periodLevel']),e['period'],e['project']) for e in result.getDict()['Element_Info'].values() ]) + cls.store[run] = sorted([ (int(e['periodLevel']),e['period'],e['project']) for e in result['Element_Info'].values() ]) + except: + print "Exception" + cls.store[run] = [] + return [x[1] for x in cls.store[run]] + + + @classmethod + def get_periods(cls, year, level): + try: + cmd = ['ListDataPeriods', '-createdSince=2009-01-01 00:00:00' ] + if year>2000: + cmd += [ '-projectName=data%02i%%' % (year-2000) ] + if level in [1,2,3]: + cmd += [ '-periodLevel=%i' % level ] + #result = cls.amiclient().execute(cmd) + result = cls.amiexec(cmd) + #rows = [ (e['period'], e['projectName']) for e in result.getDict()['Element_Info'].values() ] + rows = [ (e['period'], e['projectName']) for e in result['Element_Info'].values() ] + return sorted(rows) + except Exception , e: + print e + return [] + + + @classmethod + def get_all_periods(cls): + if cls.all_periods != None: return cls.all_periods + cls.all_periods = [] + p = re.compile("(?P<period>(?P<periodletter>[A-Za-z]+)(?P<periodnumber>\d+)?)$") + try: + result = cls.get_periods(0, 0) + for period, projectName in result: + m = p.match(period) + if not m: continue + year = int(projectName[4:6]) + period_letter = m.group('periodletter') + period_number = int(m.group('periodnumber')) if m.group('periodnumber') else 0 + if len(period_letter)!=1: pc = 0 + else: pc = 10000*year + 100*(ord(period_letter.upper())-65) + period_number + cls.all_periods += [ ((year, period, pc), projectName+".period" + period) ] + cls.all_periods.sort() + except Exception, e: + print e + pass + return cls.all_periods + + + @classmethod + def get_runs(cls, period, year): + try: + cmd = ['GetRunsForDataPeriod', '-period=%s' % period] + if year>2000: + cmd += [ '-projectName=data%02i%%' % (year-2000) ] + #result = cls.amiclient().execute(cmd) + result = cls.amiexec(cmd) + #print "amiCommand",' '.join(cmd) + #r = sorted([ int(e['runNumber']) for e in result.getDict()['Element_Info'].values() ]) + r = sorted([ int(e['runNumber']) for e in result['Element_Info'].values() ]) + return r + except: pass + + + +# for testing purpose +if __name__ == "__main__": + + choice = 1 + while choice != 0: + print "\n1 - periods for run" + print "2 - runs for period (and year)" + print "3 - periods (by year and/or level)" + print "4 - all periods (different format)" + print "5 - test AMI authentication" + print "\n0 - exit\n" + + choice = raw_input(" enter your choice: ") + choice = int(choice) if choice.isdigit() else 0 + if choice==1: + run = int(raw_input(" run number: ")) + print ARQ_AMI.get_periods_for_run( [run] ) + elif choice==2: + period = raw_input(" period : ") + year = raw_input(" year <RET> = all : ") + year = int(year) if year.isdigit() else 0 + print ', '.join([str(x) for x in ARQ_AMI.get_runs(period, year)]) + elif choice==3: + year = raw_input(" year <RET> = all : ") + year = int(year) if year.isdigit() else 0 + period = raw_input(" period [1|2|3] <RET> = all : ") + period = int(period) if period.isdigit() else 0 + print ARQ_AMI.get_periods(year, period) + elif choice==4: + print ARQ_AMI.get_all_periods() + elif choice==5: + ami = ARQ_AMI.amiclient() + if ami!="No AMI": + print "Successfully connected to AMI" + else: + print "Failed to connect to AMI" diff --git a/Database/CoolRunQuery/python/AtlRunQueryCOMA.py b/Database/CoolRunQuery/python/AtlRunQueryCOMA.py new file mode 100755 index 00000000000..0d16b1a7b28 --- /dev/null +++ b/Database/CoolRunQuery/python/AtlRunQueryCOMA.py @@ -0,0 +1,178 @@ +#!/bin/env python + +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration +# +# ---------------------------------------------------------------- +# Script : AtlRunQueryAMI.py +# Project: AtlRunQuery +# Purpose: Utility to retrieve information from AMI +# Authors: Andreas Hoecker (CERN), Joerg Stelzer (DESY) +# Created: Nov 10, 2009 +# ---------------------------------------------------------------- +# + +import re + +class ARQ_COMA: + + __store = {} + all_periods = None + + __cursor = None + __schema = "ATLAS_TAGS_METADATA" + + __tables = { + 'PR' : 'COMA_V_PERIOD_RUNS', + } + + @classmethod + def getUsedTables(cls, output, condition): + usedtables = set( [o.split('.')[0] for o in output] ) # all tables specified in output fields + + for c in condition: + for p in c.split(): + if '.' in p and not '\'' in p: usedtables.add(p.split('.')[0].lstrip('(')) # all tables specified in conditions + + return ["%s.%s %s" % (cls.__schema,cls.__tables[t],t) for t in usedtables] # prefix schema name to table name + + + @classmethod + def query(cls, output, condition, bindvars, verbose=False): + query = 'select distinct %s from %s' % (', '.join(output), + ', '.join(cls.getUsedTables(output, condition))) + if condition: + query += ' where ' + ' and '.join(condition) + if verbose: print "="*80,"\n",query + c = cls.cursor() + c.execute(str(query),bindvars) + return c.fetchall() + + + @classmethod + def cursor(cls): + """get COMA connection + any connection to ATLR is fine, we use Trigger DB + """ + if not cls.__cursor: + from TrigConfigSvc.TrigConfigSvcUtils import getTriggerDBCursor + cls.__cursor = getTriggerDBCursor("TRIGGERDB")[0] + return cls.__cursor + + + + + + @classmethod + def get_periods_for_run(cls, run): + try: run = int(run) + except: return [] + if not run in cls.__store: + + print "FRESH Access for run" ,run + + output = ['PR.P_LEVEL', 'PR.P_PERIOD', 'PR.P_PROJECT' ] + condition = [ "PR.RUN_INDEX = :run" ] + bindvars = { "run": run } + + cls.__store[run] = sorted( cls.query(output, condition, bindvars) ) + + return [x[1] for x in cls.__store[run]] + + + @classmethod + def get_periods(cls, year=0, level=0): + output = ['PR.P_PERIOD', 'PR.P_PROJECT' ] + condition = [] + bindvars = {} + + if level>0: + condition += [ "PR.P_LEVEL=:lvl" ] + bindvars["lvl"] = level + + if year>2000: + project = 'data%02i%%' % (year-2000) + condition += [ "PR.P_PROJECT like :proj" ] + bindvars["proj"] = project + + return sorted( cls.query(output, condition, bindvars) ) + + + @classmethod + def get_all_periods(cls): + if cls.all_periods != None: return cls.all_periods + cls.all_periods = [] + p = re.compile("(?P<period>(?P<periodletter>[A-Za-z]+)(?P<periodnumber>\d+)?)$") + try: + result = cls.get_periods(0, 0) + for period, projectName in result: + m = p.match(period) + if not m: continue + year = int(projectName[4:6]) + period_letter = m.group('periodletter') + period_number = int(m.group('periodnumber')) if m.group('periodnumber') else 0 + if len(period_letter)!=1: pc = 0 + else: pc = 10000*year + 100*(ord(period_letter.upper())-65) + period_number + cls.all_periods += [ ((year, period, pc), projectName+".period" + period) ] + cls.all_periods.sort() + except Exception, e: + print e + pass + return cls.all_periods + + + @classmethod + def get_runs(cls, period, year): + output = ['PR.RUN_INDEX', 'PR.P_LEVEL', 'PR.P_PERIOD', 'PR.P_PROJECT' ] + + condition = [ "PR.P_PERIOD=:period" ] + bindvars = { "period" : period } + if year>2000: + project = 'data%02i%%' % (year-2000) + condition += [ "PR.P_PROJECT like :proj" ] + bindvars["proj"] = project + result = cls.query(output, condition, bindvars) + + tmpstore = {} + + for record in result: + run = record[0] + info = record[1:4] + if not run in tmpstore: + tmpstore[run] = [] + tmpstore[run] += [info] + + cls.__store.update(tmpstore) + + return sorted( [r[0] for r in result] ) + + + +# for testing purpose +if __name__ == "__main__": + + choice = 1 + while choice != 0: + print "\n1 - periods for run" + print "2 - runs for period (and year)" + print "3 - periods (by year and/or level)" + print "4 - all periods (different format)" + print "\n0 - exit\n" + + choice = raw_input(" enter your choice: ") + choice = int(choice) if choice.isdigit() else 0 + if choice==1: + run = int(raw_input(" run number: ")) + print ARQ_COMA.get_periods_for_run( run ) + elif choice==2: + period = raw_input(" period : ") + year = raw_input(" year <RET> = all : ") + year = int(year) if year.isdigit() else 0 + print ', '.join([str(x) for x in ARQ_COMA.get_runs(period, year)]) + elif choice==3: + year = raw_input(" year <RET> = all : ") + year = int(year) if year.isdigit() else 0 + level = raw_input(" level [1|2|3] <RET> = all : ") + level = int(level) if level.isdigit() else 0 + print ARQ_COMA.get_periods(year, level) + elif choice==4: + print ARQ_COMA.get_all_periods() diff --git a/Database/CoolRunQuery/python/AtlRunQueryLib.py b/Database/CoolRunQuery/python/AtlRunQueryLib.py new file mode 100755 index 00000000000..ef0598d3f20 --- /dev/null +++ b/Database/CoolRunQuery/python/AtlRunQueryLib.py @@ -0,0 +1,349 @@ +#!/bin/env python + +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration +# +# ---------------------------------------------------------------- +# Script : AtlRunQueryLib.py +# Project: AtlRunQuery +# Purpose: Library with the actual query classes +# Authors: Andreas Hoecker (CERN), Joerg Stelzer (DESY) +# Created: Nov 13, 2008 +# ---------------------------------------------------------------- +# + +from __future__ import with_statement + +from CoolRunQuery.utils.AtlRunQueryTimer import timer + +import CoolRunQuery.utils.AtlRunQueryFastBlobRead + +import cx_Oracle + +try: + Set = set +except NameError: + from sets import Set + +import sys, re, os +from datetime import timedelta,datetime +from PyCool import cool +from time import time,strftime,gmtime +from collections import defaultdict +from copy import deepcopy + +from utils.AtlRunQueryUtils import coolDbConn, runsOnServer + +from selector.AtlRunQuerySelectorRuntime import RunTimeSelector, TimeRunSelector +from selector.AtlRunQuerySelectorBase import Selector, DataKey + +from CoolRunQuery.AtlRunQueryRun import Run +from CoolRunQuery.AtlRunQueryQueryConfig import QC + + + + + +# ----------------------------------------------------------------------------------------- +# class: AtlRunQuery +# ----------------------------------------------------------------------------------------- + +class AtlRunQuery: + + def __init__(self, options, readoracle=False, loglevel=1, html="AUTO", origQuery="", datapath='data', parsedstring=""): + + with timer('total'): + + with timer('config'): + self.config(options, readoracle, loglevel, html, origQuery, datapath, parsedstring) + + with timer('run all'): + runlist = self.run() + + with timer('evaluate all'): + (dic, dicsum, xmlhtmlstr, roothtmlstr) = self.evaluate(runlist) + + with timer('output all'): + self.output(runlist, dic, dicsum, xmlhtmlstr, roothtmlstr) + + with timer('finalize'): + self.finalize() + + from CoolRunQuery.utils.AtlRunQueryTimer import TimerStats as TS + TS.printTimeSummary() + TS.printTimeFlat() + + + + def config(self, options, readoracle, loglevel, html, origQuery, datapath, parsedstring): + + self.cmdlineOptions = options + self.origQuery = origQuery + self.datapath = datapath + Run.Datapath = self.datapath + QC.datapath = self.datapath # TODO: move away from Run.Datapath to QC.datapath + self.parsedstring = parsedstring + self.maxNumOfRuns = -1 # in case a maximum number of to be selected runs is given + self.makeSummary = 'summary' in options.show # in case a summary shall be printed + self.makeDQSummary = 'dqsummary' in options.show # in case a DQ efficiency summary shall be printed + self.makeDQPlots = 'dqplots' in options.show # in case only DQ plots shall be printed + self.makeDQeff = 'dqeff' in options.show # in case a DQ efficiency summary shall be printed + self.xmlFileName = 'MyLBCollection.xml' + self.xmlFileLabel = 'MyLBCollection' + self.querystart = time() + self.selectionOutput = [] + + # if no show argument is specified, we use the minimal version + if not self.cmdlineOptions.show: + self.cmdlineOptions.show = ['run','time'] + + if self.cmdlineOptions.xmlfile != None: + # format 'file.xml:runlist' or 'runlist:file.xml' + str1,str2 = self.cmdlineOptions.xmlfile.strip().split(':',1) + if '.xml' in str1: + self.xmlFileName = str1 + if str2 != '': self.xmlFileLabel = str2 + elif '.xml' in str2: + self.xmlFileName = str2 + if str1 != '': self.xmlFileLabel = str1 + + if self.cmdlineOptions.html: + self.html = self.cmdlineOptions.html + else: + if html=="AUTO": self.html = runsOnServer() + elif html=="YES": self.html = True + elif html=="NO": self.html = False + else: raise RuntimeError, "Unknown argument for option 'html': %s" % html + Run.writehtml = self.html + + if self.cmdlineOptions.prodgrl != None : # happens only if --nogrl is specified + self.prodgrl = self.cmdlineOptions.prodgrl + elif self.cmdlineOptions.xmlfile != None : + self.prodgrl = True # if an xml file is specified it will be produced + else: + self.prodgrl = not runsOnServer() + + + if self.cmdlineOptions.dictroot != None : # happens if --root or --noroot is specified + self.dictroot = self.cmdlineOptions.dictroot + else: + self.dictroot = not runsOnServer() + + + if self.cmdlineOptions.database: + if self.cmdlineOptions.database.upper()=='MC': Selector.db = 'OFLP200' + elif self.cmdlineOptions.database.upper()=='DATA': Selector.db = 'COMP200' + else: Selector.db = self.cmdlineOptions.database + + if self.cmdlineOptions.condtag: + Selector.condtag = self.cmdlineOptions.condtag + + QC.localtime = (self.cmdlineOptions.utc==None) + QC.settimezone() + + # check which runs currently participate in prompt calibration + # we need this for the web display only + if self.html: + nemo_dir = '/afs/cern.ch/user/a/atlcond/scratch0/nemo/prod/web/' + f = open( nemo_dir + 'calibruns.txt' ) + for line in f: + try: + if line: Run.PromptCalibRuns.append( int(line.strip()) ) + except ValueError: + pass + # read list of NEMO tasks + # do we need this for each query, or just for the web?? + fname = os.listdir( nemo_dir + 'tasks' ) + i = 0 + for taskname in fname: + m = re.match('.*(task_(\d+)_\d+.txt)',taskname) + if m: + i+=1 + fname, runnr = m.groups() + Run.NemoTasks[int(runnr)] = fname + + + + def run(self): + # is last run still open ? + from CoolRunQuery.AtlRunQuerySFO import GetSFO_lastNruns + sfoconnection = coolDbConn.GetSFODBConnection() + sfocursor = sfoconnection.cursor() + retlist = GetSFO_lastNruns( sfocursor, 1 ) + if 'OPENED' in retlist[0][1]: Run.runnropen = retlist[0][0] + + if self.cmdlineOptions.runlist != None: + runlist = self.cmdlineOptions.runlist + if any('last' in x for x in self.cmdlineOptions.runlist): + for idx in xrange(len(runlist)): + rrange = runlist[idx] + if not 'last' in rrange: continue + rrange = rrange.replace('last','') + self.maxNumOfRuns = int(rrange) + # list of last n runs run numbers, where at least one stream is not calibration + retlist = GetSFO_lastNruns( sfocursor, 1.1*self.maxNumOfRuns ) + runlist[idx] = '%i+' % (retlist[-1][0]) + + elif self.cmdlineOptions.timelist != None: + # translate time into run-range + if 'last' in self.cmdlineOptions.timelist[0]: + # contains 'last' + from CoolRunQuery.utils.AtlRunQueryUtils import get_runs_last_dt + rlist = get_runs_last_dt(self.cmdlineOptions.timelist[0]) + + runlist = ['%i+' % rlist[-1]] if (len(rlist)>0) else ['99999999+'] + + else: + # translate time into run-range + from utils.AtlRunQueryUtils import timeStringToSecondsUTC,secondsToTimeStringUTC,get_run_range2,GetTimeRanges + timelist = ','.join(self.cmdlineOptions.timelist) + timeranges,timerangesHR = GetTimeRanges(timelist, intRepFnc=timeStringToSecondsUTC, maxval=time()) + timerangesAsString = [ map(secondsToTimeStringUTC, tr) for tr in timeranges] + runranges = [ ( "%s-%s" % get_run_range2(tr[0],tr[1]) ) for tr in timerangesAsString] + runlist = [','.join(runranges)] + + + # the run selector + rtSel = RunTimeSelector(name = 'runtime', runlist = runlist) + + # find the begin and end of each interesting run + runlist = rtSel.select() + self.selectionOutput += ["%s" % rtSel] + + + from .AtlRunQuerySelectorWorker import SelectorWorker + + # create all selectors that actively select + SelectorWorker.parseSelectorOptions(self.cmdlineOptions) + + # parse show option: + SelectorWorker.parseShowOption(self.cmdlineOptions) + + # call setApply for all selectors that actively select + SelectorWorker.setApplySelection() + + # call setShowOutput for all selectors that show + SelectorWorker.setShowOutput() + + SelectorWorker.getRetrieveSelector('trigkey','TrigKeySelector') + + # Selectors can implement an initialize function which runs after any constructor and setShow + for s in SelectorWorker.getOrderedSelectorList(): + s = s.selector + if hasattr(s, 'initialize'): + with timer("initializing selector '%s'" % s.name): + s.verbose = True + s.initialize() + + # apply the selectors to initial run list + for s in SelectorWorker.selectors(): + with timer("run selector '%s'" % s.name): + s.verbose = True + runlist = s.select(runlist) + self.selectionOutput += ["%s" % s.__str__()] + with timer("run AfterQuery for selector '%s'" % s.name): + s.runAfterQuery(runlist) + #print [r.runNr for r in runlist] + + # reverse list for presentation: newest run on top + runlist.reverse() + + # if "last" option used, the runlist has to be truncated + if self.maxNumOfRuns > 0: + print 'SELOUT Selecting the %i most recent runs' % self.maxNumOfRuns + runlist = runlist[0:self.maxNumOfRuns] + self.selectionOutput += ['SELOUT Selecting the %i most recent runs' % self.maxNumOfRuns] + + return runlist + + + def evaluate(self, runlist): + # provide pickled dictionary of query result + with timer('CreateResultDict'): + from CoolRunQuery.output.AtlRunQuerySave import CreateResultDict + dic, dicsum = CreateResultDict( runlist ) + + + # create XML file (and return pretty html output for print) + with timer('CreateXMLFile'): + if self.prodgrl: + from CoolRunQuery.output.AtlRunQueryXML import CreateXMLFile + from .AtlRunQueryVersion import SvnVersion + xmlhtmlstr = CreateXMLFile( runlist, self.cmdlineOptions, self.origQuery, self.datapath, + self.xmlFileName, self.xmlFileLabel, SvnVersion ) + else: + xmlhtmlstr = None + print "Creation of GRL disabled" + + with timer('CreateRootFile'): + if self.dictroot and len(runlist) > 0: + # create root file from dictionary - and make plots + from CoolRunQuery.output.AtlRunQueryRoot import CreateRootFile + rootfilename, roothtmlstr = CreateRootFile( dic ) + else: + roothtmlstr = None + print "Creation of root file disabled" + + return (dic, dicsum, xmlhtmlstr, roothtmlstr) + + + def output(self, runlist, dic, dicsum, xmlhtmlstr, roothtmlstr): + + if self.html: + + with timer('GetOKS link info'): + from AtlRunQuerySFO import SetOKSLinks + SetOKSLinks(runlist) + + # create web page + from .html.AtlRunQueryHTML import ResultPageMaker + try: + pageinfo = { 'datapath' : self.datapath, + 'origQuery' : self.origQuery, + 'fullQuery' : self.parsedstring, + 'runlist' : runlist, + 'dic' : dic, + 'dicsum' : dicsum, + 'makeSummary' : self.makeSummary, + 'makeDQeff' : self.makeDQeff, + 'makeDQSummary' : self.makeDQSummary, + 'makeDQPlots' : self.makeDQPlots, + 'roothtmlstr' : roothtmlstr, + 'xmlfilename' : self.xmlFileName, + 'xmlhtmlstr' : xmlhtmlstr, + 'querytime' : time() - self.querystart, + 'selout' : self.selectionOutput, + } + with timer("run page maker"): + ResultPageMaker.makePage(pageinfo) + except ImportError, ie: + print "Can't import pagemaker, no web page made",ie + + else: + print '---------------------------------------------------------------------' + print Run.header() + runlist.reverse() + for r in runlist: + print r + + from CoolRunQuery.utils.AtlRunQueryUtils import addKommaToNumber, filesize + print 'Summary:' + for data_key, summary in dicsum.items(): + if data_key.Type==DataKey.STREAM: + key = data_key.ResultKey.replace('STR:','') + val = '%s (%s)' % ( addKommaToNumber(summary[0]), filesize(summary[1]) ) + else: + key = data_key.ResultKey + val = addKommaToNumber(summary) + print '%20s : %s' % (key,val) + duration = time() - self.querystart + print "%20s : %g sec" % ('Total execution time',duration) + print '---------------------------------------------------------------------' + + def finalize(self): + coolDbConn.CloseAll() + +if __name__ == '__main__': + from CoolRunQuery.AtlRunQueryOptions import AtlRunQueryOptions + (options, args) = AtlRunQueryOptions().parse() + rsq = AtlRunQuery(options) + diff --git a/Database/CoolRunQuery/python/AtlRunQueryLookup.py b/Database/CoolRunQuery/python/AtlRunQueryLookup.py new file mode 100644 index 00000000000..6d3ea110385 --- /dev/null +++ b/Database/CoolRunQuery/python/AtlRunQueryLookup.py @@ -0,0 +1,3 @@ +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + +from utils.AtlRunQueryLookup import DQChannelDict diff --git a/Database/CoolRunQuery/python/AtlRunQueryOptions.py b/Database/CoolRunQuery/python/AtlRunQueryOptions.py new file mode 100644 index 00000000000..56556cfb013 --- /dev/null +++ b/Database/CoolRunQuery/python/AtlRunQueryOptions.py @@ -0,0 +1,237 @@ +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + +import sys +from optparse import OptionParser + +class AtlRunQueryOptions: + def __init__(self): + self.parser = OptionParser(usage="usage: %prog options") + self.parser.add_option( "-r", + "--run", + action = 'append', + dest = "runlist", + help = "A list of run ranges 90923,89500-89600,92050-" ) + + self.parser.add_option( "-t", + "--time", + action="append", + dest = "timelist", + help = "A list of time ranges 10.9.2008-30.9.2008,15.10.2008-" ) + + self.parser.add_option( "-p", + "--partition", + dest = "partition", + help = "The partition name [default: %DEFAULT]" ) + + self.parser.add_option( "--readyforphysics", + dest = "readyforphysics", + help = "ReadyForPhysics flag" ) + + self.parser.add_option( "--db", + dest = "database", + default = 'COMP200', + choices = ['COMP200', 'OFLP200', 'DATA', 'MC'], + help = "The detabase [default: %DEFAULT]" ) + + self.parser.add_option( "-c", + "--ctag", + dest = "condtag", + help = "The top conditions tag" ) + + self.parser.add_option( "-l", + "--duration", + dest = "duration", + help = "The duration limit of a run in seconds e.g.: -l 36000 (longer than 10 hours) or -l 300- (less than 5 minutes)" ) + + self.parser.add_option( "-e", + "--events", + dest = "events", + help = "Number of events (EF accepts) in run e.g. 10000-20000" ) + + self.parser.add_option( "--allevents", + dest = "allevents", + help = "Shows number of EFA/SFO/L1A/L2A events in selected runs e.g. 10000-20000" ) + + self.parser.add_option( "-k", + "--smk", + dest = "smklist", + help = "A list of smks 359,365,372-" ) + + self.parser.add_option( "-b", + "--bfield", + action = "append", + choices = ['on', 'off', 'solenoidon', 'solenoidoff', 'toroidon', 'toroidoff'], + dest = "bfield", + help = "Bfield ['on', 'off', 'solenoidon', 'solenoidoff', 'toroidon', 'toroidoff']" ) + + self.parser.add_option( "-q", + "--dq", + action = "append", + dest = "dqchannels", + help = "Data quality flags, e.g. --dq 'PIXB green SHIFTOFL'" ) + + self.parser.add_option( "--detmaskin", + action = "append", + dest = "detmaskin", + help = "Detector mask, e.g. --detmaskin 0x3123 " ) + + self.parser.add_option( "--detmaskout", + action = "append", + dest = "detmaskout", + help = "Detector mask of absent detectors, e.g. --detmaskout 0x2174 " ) + + self.parser.add_option( "--filenametag", + dest = "projecttag", + help = "Filename tag, e.g. --filenametag !data_test " ) + + self.parser.add_option( "--projecttag", + dest = "projecttag", + help = "Project tag, e.g. --projecttag !data_test " ) + + self.parser.add_option( "-s", + "--show", + action = "append", + dest = "show", + help = "List of items to query" ) + + self.parser.add_option( "--streams", + action = "append", + dest = "streams", + help = 'Streams, --streams "phy*" ' ) + + self.parser.add_option( "--trigger", + action = "append", + dest = "trigger", + help = 'Triggers, --trigger "EF_Electron*" ' ) + + self.parser.add_option( "--release", + action = "append", + dest = "release", + help = 'Release, --release "14.2.5-14.3.1" ' ) + + self.parser.add_option( "--luminosity", + action = "append", + dest = "luminosity", + help = 'Luminosity, --luminosit' ) + + self.parser.add_option( "--olclumi", + action = "append", + dest = "olclumi", + help = 'olclumi, --olclumi "1ub+"' ) + + self.parser.add_option( "--olcfillparams", + action = "append", + dest = "olcfillparams", + help = 'olcfillparams, --olcfillparams "channel"' ) + + self.parser.add_option( "--beamspot", + action = "append", + dest = "beamspot", + help = 'Beamspot, --beamspot' ) + + self.parser.add_option( "--bpm", + action = "append", + dest = "bpm", + help = 'BPM, --bpm' ) + + self.parser.add_option( "--datasets", + action = "append", + dest = "datasets", + help = 'Datasets, --datasets *NTUP*' ) + + self.parser.add_option( "--ami", + action = "append", + dest = "ami", + help = 'Ami, --ami' ) + + self.parser.add_option( "--larcond", + action = "append", + dest = "larcond", + help = 'Larcond, --larcond "nsamples 7"' ) + + self.parser.add_option( "--lhc", + action = "append", + dest = "lhc", + help = 'LHC information, --lhc "fillnumber 851"' ) + + self.parser.add_option( "--summary", + action = "count", + dest = "summary", + help = 'Print summary, --summary ' ) + + self.parser.add_option( "--dqeff", + action = "count", + dest = "dqeff", + help = 'Print DQ efficiency summary, --dqeff ' ) + + self.parser.add_option( "-v", + "--verbose", + action = "count", + dest = "verbose", + help = "Verbosity level" ) + + self.parser.add_option( "--noroot", + action = "store_false", + dest = "dictroot", + help = "Do not create root files and python dict" ) + + self.parser.add_option( "--root", + action = "store_true", + dest = "dictroot", + help = "Do create root files and python dict" ) + + self.parser.add_option( "--nohtml", + action = "store_false", + dest = "html", + help = "Text output instead of html" ) + + self.parser.add_option( "--html", + action = "store_true", + dest = "html", + help = "Text output instead of html" ) + + self.parser.add_option( "--selectOnUnknown", + action = "store_true", + dest = "selectonunknown", + default = True, + help = "Select when information is not known (only for #evt and partition name)" ) + + self.parser.add_option( "--skipUnknown", + action = "store_false", + dest = "selectonunknown", + help = "Select when information is not known (only for #evt and partition name)" ) + + self.parser.add_option( "--xmlfile", + action = "store", + type = "string", + dest = "xmlfile", + help = "Give filename and label (format: filename:label) for XML GoodRunList file" ) + + self.parser.add_option( "--nogrl", + action = "store_false", + dest = "prodgrl", + help = "Do not produce a good run list" ) + + self.parser.add_option( "--utc", + action = "store_true", + dest = "utc", + help = "Use utc for everything, default is local time (CET/CEST)" ) + + + def splitCmdline(self,argstring): + arg = argstring.split() + m = zip([x for x in xrange(len(arg)) if arg[x][0]=='"'],[x for x in xrange(len(arg)) if arg[x][-1]=='"']) + m.reverse() + for p in m: + if p[0]==p[1]: arg[p[0]] = arg[p[0]].strip('"') + elif p[1]-p[0] == 2: + arg[p[0]:p[1]+1]=[("%s %s %s" % (arg[p[0]],arg[p[0]+1],arg[p[1]])).strip('"')] + else: + arg[p[0]:p[1]+1]=[("%s %s" % (arg[p[0]],arg[p[1]])).strip('"')] + return arg + + + def parse(self,atlqueryarg=None): + if atlqueryarg: + sys.argv = self.splitCmdline(atlqueryarg) + return self.parser.parse_args() diff --git a/Database/CoolRunQuery/python/AtlRunQueryPVSS.py b/Database/CoolRunQuery/python/AtlRunQueryPVSS.py new file mode 100644 index 00000000000..df13a2ba798 --- /dev/null +++ b/Database/CoolRunQuery/python/AtlRunQueryPVSS.py @@ -0,0 +1,34 @@ +#!/bin/env python + +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration +# +# ---------------------------------------------------------------- +# Script : AtlRunQueryPVSS.py +# Project: AtlRunQuery +# Purpose: Utility to retrieve information from PVSS DB (DCS quantities) +# Authors: Andreas Hoecker (CERN), Joerg Stelzer (DESY) +# Created: Mar 27, 2010 +# ---------------------------------------------------------------- + +import cx_Oracle +from time import time + +def GetPVSS_BPMs( cursor, iovmin, iovmax ): + + # fetch the event history first + selcmd = "SELECT 'EVENTHISTORY_'||lpad (archive#,8,'0') from ( select archive#, start_time, end_time from atlas_pvssDCS.arc_archive A where A.group_name = 'EVENT' and (A.end_time > to_date('%s','DD-MM-YYYY HH24:MI:SS') or A.end_time is null) intersect select archive#, start_time, end_time from atlas_pvssDCS.arc_archive A where A.group_name = 'EVENT' and A.start_time < to_date('%s','DD-MM-YYYY HH24:MI:SS') )" % (iovmin, iovmax) + cursor.execute( selcmd ) + eventhistory = cursor.fetchone()[0] + + # perform the actual query + res = {} + bpmvars = ['ATLGCSLHC:BPMSW_B1_distanceIP.position.horizontal', 'ATLGCSLHC:BPMSW_B1_distanceIP.position.vertical', + 'ATLGCSLHC:BPMSW_B2_distanceIP.position.horizontal', 'ATLGCSLHC:BPMSW_B2_distanceIP.position.vertical'] + + for var in bpmvars: + selcmd = "SELECT ROUND(A.VALUE_NUMBER,8), TO_CHAR(A.TS, 'DD-MM-YYYY HH24:MI:SS:FF3') FROM ATLAS_PVSSDCS.%s A,ATLAS_PVSSDCS.ELEMENTS B WHERE A.ELEMENT_ID = B.ELEMENT_ID and ROWNUM <= 15000 AND A.ELEMENT_ID = ( SELECT ELEMENT_ID FROM ATLAS_PVSSDCS.ELEMENTS WHERE ELEMENT_NAME LIKE '%s') AND TS BETWEEN TO_DATE('%s','DD-MM-YYYY HH24:MI:SS' ) AND TO_DATE('%s','DD-MM-YYYY HH24:MI:SS' ) AND A.VALUE_NUMBER <> 'NaN' AND A.VALUE_NUMBER <> BINARY_FLOAT_INFINITY ORDER BY B.ELEMENT_NAME, A.TS" % (eventhistory, var, iovmin, iovmax) + cursor.execute( selcmd ) + res[var] = cursor.fetchall() + + return res + diff --git a/Database/CoolRunQuery/python/AtlRunQueryParser.py b/Database/CoolRunQuery/python/AtlRunQueryParser.py new file mode 100755 index 00000000000..2efa7672e4a --- /dev/null +++ b/Database/CoolRunQuery/python/AtlRunQueryParser.py @@ -0,0 +1,839 @@ +#!/usr/bin/env python + +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration +# +# ---------------------------------------------------------------- +# Script : AtlRunQueryParser.py +# Project: AtlRunQuery +# Purpose: Utility to translate a parser language (to be used in +# a web browser) into AtlRunQuery arguments +# Authors: Andreas Hoecker (CERN), Joerg Stelzer (DESY) +# Created: Nov 13, 2008 +# ---------------------------------------------------------------- +# +import sys,os,re +import urllib + +from utils.AtlRunQueryLookup import InitDetectorMaskDecoder, DecodeDetectorMask, DQChannels + +class ArgumentParser: + dqFolderList = [ 'SHIFTOFL', 'DQCALCOFL', 'DQMFOFL', 'DQMFOFLH', 'DCSOFL', 'TISUMM', 'LBSUMM', 'MUONCALIB', + 'DQMFONL', 'DQMFONLLB', 'SHIFTONL' ] # these are online folders + + def __init__ ( self ): + self.const_arg = "" + self.default_find = "" + self.isMCDB = False + self.default_show = ["run","events","time"] + + # allowed 'query' arguments + # tuple format: ( "short name", "long-name", "interpret function", "show function", "example", "default value" ) + self.sortedKeys = [ "run", "time", "duration", "events", "allevents", "projectTag", "partition", "readyforphysics", + "trigkeys", "trigger", "release", + "ami", "magnets", "larcond", "db", "ctag", "lhc", "lumi", "dq", + "streams", "detector", "datasets", "all", "summary", "dqeff" ] + + self.queryArgs = { "run": ("r(uns)", "run", self.InterpretPeriods, self.ShowVariable, + 'r(uns) [format: "run 91000", "runs 91000-92000", "runs 91000-(+)" (this run and all before (after))]', ""), + "all": ("all", "all", self.InterpretString, self.ShowVariable, + 'all [format: only available as "show" option]', ""), + "summary": ("summary", "summary", self.InterpretString, self.ShowVariable, + 'summary [format: only available as "show" option]', ""), + "dqsummary": ("dqsum(mary)", "dqsummary", self.InterpretString, self.ShowVariable, + 'dqsum(mary) [format: only available as "show" option]', ""), + "dqplots": ("dqpl(ots)", "dqplots", self.InterpretString, self.ShowVariable, + 'dqpl(ots) [format: only available as "show" option]', ""), + "dqeff": ("dqeff", "dqeff", self.InterpretString, self.ShowVariable, + 'dqeff [format: only available as "show" option]', ""), + "events": ("ev(ents)", "events", self.InterpretRange, self.ShowVariable, + 'ev(ents) [format: "events 10000+", ... (same as for run number)]', ""), + "allevents": ("alle(vents)", "allevents", self.InterpretRange, self.ShowVariable, + 'alle(vents) [format: only available as "show" option]', ""), + "time": ("t(ime)", "time", self.InterpretRange, self.ShowVariable, + 't(ime) [format: "time 10.9.2008-30.9.2008", "time 15.10.2008-(+)" (this date and all before (after))]', ""), + "duration": ("dur(ation)", "duration", self.InterpretDuration, self.ShowVariable, + 'dur(ation) [format: "dur 10d/h/m/s+/-", "2000s+" (run duration more than 2000 sec), "dur 3h-" (less than 3 hours)]', ""), + "magnets": ("m(agnet)", "bfield", self.InterpretMagnets, self.ShowVariable, + 'm(agnet) [format: "magnet s(olenoid)", "magnet t(oroid)", "not magnet t(oroid)", ...]', ""), + "detector": ("det(ector)", "detmask", self.InterpretDetectorMask, self.ShowVariable, + 'det(ector) [format: "detector Pixel B" (for Pixel Barrel), "detector Pixel" (for Pixel B & EC), "all" (for all detectors, detector mask = 72551459979255)', ""), + "smk": ("smk", "smk", self.InterpretRange, self.ShowVariable, + 'smk [format: "smk 398", "smk 398,399 (super-master-key labelling trigger menu, format like for run ranges)]', ""), + "trigkeys": ("trigk(eys)", "trigkeys", self.InterpretRange, self.ShowVariable, + 'trigk(eys) [format: "tkeys" (show all trigger keys of run: SMK, L1 and HLT prescale keys)]', ""), + "release": ("rel(ease)", "release", self.InterpretString, self.ShowWithArg, + 'rel(ease) [format: "release 15.1.*" ',""), + "projectTag": ("ptag", "projecttag",self.InterpretString, self.ShowVariable, + 'ptag [format: "ptag data08_cos,data08_cosmag,data09_cos", "ptag data08_cos*,data09_cos" (note: the projectTag in dataset name / denoted "filenamtTag" in COOL)]',"data08*,data09*,data10*,data11*,data12*,data13*,data14*,data15*,data16*"), + "partition": ("p(artition)", "partition", self.InterpretString, self.ShowVariable, + 'p(artition) [format: "partition ATLAS"]', "ATLAS"), + "readyforphysics": ("ready(forphysics)", "readyforphysics", self.InterpretString, self.ShowVariable, + 'ready(forphysics) [format: "readyforphysics T(rue)"]', ""), + "db": ("db", "db", self.InterpretString, self.ShowVariable, + 'db [format: "db <DB>, where <DB> is either DATA, MC, COMP200, or OFLP200',""), + "ctag": ("ctag", "ctag", self.InterpretString, self.ShowVariable, + 'ctag [format: "ctag COMCOND-HLTC-001-00" ',""), + "streams": ("st(reams)", "streams", self.InterpretStreams, self.ShowWithArg, + 'st(reams) [format: "stream RPCwBeam,TGCwBeam", "stream physics*" ',""), + "dq": ("dq", "dq", self.InterpretDQ, self.ShowDQ, + 'dq [format: "dq <FLAG> <status>", where the data quality status is "g(reen)", "y(ellow)", "r(ed)", "u(known)", example: dq PIXB y+ (means yellow or green status)]', ""), + "trigger": ("tr(igger)", "trigger", self.InterpretString, self.ShowWithArg, + 'tr(igger) [format: "trigger *Electron*" ',""), + "lumi": ("lu(minosity)","luminosity", self.InterpretWithTwoArgs, self.ShowWithArg, + 'lu(minosity) [format: "lumi FOLDER", default: LBLESTOFL]" ',""), + "olc": ("olc", "olc", self.InterpretWithTwoArgs, self.ShowWithArg, + 'olc [format: "olc" (OLC = Online Luminosity Calculator)]" ',""), + "bs": ("bs", "beamspot", self.InterpretString, self.ShowWithArg, + 'bs [format: "bs"]" ',""), + "bpm": ("bpm", "bpm", self.InterpretString, self.ShowVariable, + 'bpm [format: "show bpm"]" ',""), + "datasets": ("da(tasets)", "datasets", self.InterpretString, self.ShowWithArg, + 'da(tasets) [format: show datasets *NTUP*"]" ',""), + "ami": ("ami", "ami", self.InterpretString, self.ShowVariable, + 'ami [format: show ami"]" ',""), + "larcond": ("lar(cond)", "larcond", self.InterpretWithTwoArgs, self.ShowVariable, + 'lar(cond) [format: larcond nsamples 7 / show larcond"]" ',""), + "lhc": ("lhc", "lhc", self.InterpretWithTwoArgs, self.ShowVariable, + 'lhc [format: lhc fill 7 / show lhc"]" ',""), + "trigrates": ("trigr(ates)", "trigrates", self.InterpretString, self.ShowWithArg, + 'trigr(ates) [format: sh trigrates L1_EM*"]" ',"") + } + # allowed 'show' arguments + + # init detector mask + (self.dName, self.NotInAll, self.vetoedbits) = InitDetectorMaskDecoder() + + def ParserUsage( self ): + print ' ' + print 'Parser usage: python %s <string_argument>' % sys.argv[0] + print ' ' + print 'A query starts with the keyword "f(ind)", several queries can be combined with "and", "(and) not"' + print 'The requested response information is given through the keyword "sh(ow)" (the query must be terminated by a "/")' + print ' ' + print 'Query words:' + for key in self.queryArgs: + print " %s " % self.queryArgs[key][4] + print ' ' + print 'Parts of a keyword written in paranthesis are optional' + print ' ' + print 'Examples: ' + print ' ' + print ' find run 90272 and runs 90275-91900 and magnet toroid and magnet solenoid / show run and events' + print ' f r 90272 and r 90275-91900 and m t and m s / sh r and ev [same as above in short-hand version]' + print ' find runs 90275-91900 and not run 90280 and not dq EMEC r and show run and dq EMEC' + print ' ' + + def replaceNumbers( self, n ): + return n.replace('k','000').replace('m','000000') + + def getPtagAndPeriod( self, name ): + ptag = 'data11_7TeV' + if '.' in name: ptag, period = name.split('.') + else: period = name + letter = period[period.find('period')+6:][0] + return ptag.strip(), period.strip(), letter + + def InterpretPeriods( self, atlqarg, arg, neg ): + # format: arg = 'run data10_7TeV.periodA' or 'run periodA' (where 'data11_7TeV' is assumed) + # or 'data10_7TeV.periodA-periodC' or 'data10_7TeV.periodA,data10_7TeV.periodB,...' + # This is case sensitive !! + + available_periods = [] + pfile_extension = ".runs.list" + + + def printPeriods(periods): + print '\nAvailable periods:\n' + for i,x in enumerate(periods): + print x[1],"\t", + if (i+1)%4==0: print "" + sys.exit(0) + + + def readRunListFromURL( periodname ): + wwwpath = "https://atlas.web.cern.ch/Atlas/GROUPS/DATAPREPARATION/DataPeriods/" + webaddress = wwwpath + periodname + pfile_extension + + from utils.AtlRunQueryUtils import checkURL + if not checkURL( webaddress ): + print 'ERROR: Period "%s" not existing - please check name.' % periodname + print ' Note: case sensitivity must be respected for period names!' + printPeriods(available_periods) + return [line.strip() for line in urllib.urlopen( webaddress )] + + def getDataPeriodsWithinRange(period_range): + pshort = re.compile("(?:(?:\d{2})(?P<year>\d{2})\.)?(?P<period>[a-zA-Z])(?P<subperiod>\d+)?") + m1 = pshort.match(period_range[0].upper().replace('PERIOD','')) + m2 = pshort.match(period_range[1].upper().replace('PERIOD','')) + if m1==None or m2==None: + sys.exit(0) + p1 = (int(m1.group('year')) if m1.group('year') else 11,m1.group('period'),m1.group('subperiod')) + p2 = (int(m2.group('year')) if m2.group('year') else p1[0],m2.group('period'),m2.group('subperiod')) + p1c = 10000*p1[0] + 100*(ord(p1[1].upper())-65) + (int(p1[2]) if p1[2] else 0) + p2c = 10000*p2[0] + 100*(ord(p2[1].upper())-65) + (int(p2[2]) if p2[2] else 99) + #print "P1",p1,p1c + #print "P2",p2,p2c + if p1c>p2c: sys.exit(0) + list_of_periods = [] + for p,p_name in sorted(available_periods): + if p[2]==0: continue # no special VdM or AllYear stuff + if p[2]%100==0: continue # no full periods + include = (p[2]>=p1c and p[2]<=p2c) + if include: list_of_periods += [(p[0],p[1],p_name)] # + #print p,("--> include" if include else "") + return list_of_periods + + + def getRunsFromPeriods(list_of_periods): + """list_of_periods: periods for which run nr are returned, e.g [('10','B1'),('11','A'),...]""" + runlist = [] + from CoolRunQuery.AtlRunQueryCOMA import ARQ_COMA + for year, period, fname in list_of_periods: + runlist += ARQ_COMA.get_runs(period, 2000+int(year)) + + return runlist + + arg = arg.split(None,1)[1] + + pat_last = re.compile("(?:l|la|las|last) (\d*)$") + pat_number = re.compile("\d{5,8}[+-]?$") # simple number with 5-8 digits, possibly followed by a + or - + pat_range = re.compile("\d{5,8}-\d{5,8}$") # simple number with 5-8 digits, possibly followed by a + or - + pat_short = re.compile("(?:(?:\d{2})(\d{2})\.)?([a-zA-Z]+\d*)$") + pat_full = re.compile("data(\d{2})_.*\.period([a-zA-Z]+\d*)$") + + # final result in here + list_of_runs = [] + + # are their any commas? + for tag in arg.split(','): + + # last X runs pattern + m = pat_last.match(arg) + if m: + list_of_runs += [ "last%s" % m.group(1) ] + continue + + # run numbers + if pat_number.match(tag): + list_of_runs += [tag] + continue + + # run number range + if pat_range.match(tag): + list_of_runs += [tag] + continue + + from CoolRunQuery.AtlRunQueryCOMA import ARQ_COMA + available_periods = ARQ_COMA.get_all_periods() + + period_range = tag.split('-') + if len(period_range)==2: + list_of_periods = getDataPeriodsWithinRange(period_range) + list_of_runs += getRunsFromPeriods(list_of_periods) + continue + + m=pat_short.match(tag) + if m: + year, period = m.groups() + if year==None: year="11" + period = period.upper().replace('PERIOD','') + if 'ALL' in period: period = 'AllYear' + list_of_runs += getRunsFromPeriods([(year,period,None)]) + continue + + # backward compatible form: data10_7TeV.periodA + m=pat_full.match(tag) + if m: + year, period = m.groups() + list_of_runs += getRunsFromPeriods([(year,period,tag)]) + continue + + + if len(list_of_runs)==0: + print "No runs matching pattern" + sys.exit(0) + + return "--run " + ','.join([str(r) for r in list_of_runs]) + + + + def InterpretRange( self, atlqarg, arg, neg ): + # special possibility to give run ranges + rge = self.replaceNumbers(arg.split(None,1)[1]) + rge = rge.replace('last ','l ').replace('las ','l ').replace('la ','l ').replace('l ','last ') + return "--" + atlqarg.strip() + ' "' + rge.strip().replace(' ','') + '"' + + # range interpretation utilities + def InterpretRangeNew( self, atlqarg, arg, neg ): + # special possibility to give run ranges + rge = self.replaceNumbers(arg.split(None,1)[1]) + rge = rge.replace('last ','l ').replace('las ','l ').replace('la ','l ').replace('l ','last ') + return "--" + atlqarg.strip() + ' "' + rge.strip().replace(' ','') + '"' + + def InterpretString( self, atlqarg, arg, neg ): + # '%' is translated into '*' wildcard + arg = arg.partition(' ')[2] + arg = arg.replace('%','*') + arg = arg.strip().replace(' ','') + + atlqarg = atlqarg.strip() + if atlqarg=='db' and (arg=='MC' or arg=='OFLP'): + self.isMCDB = True + + # default for ReadyForPhysics flag + if atlqarg == 'readyforphysics' and not arg: arg = '1' + + return '--%s "%s"' % (atlqarg, arg) + + def InterpretWithTwoArgs( self, atlqarg, arg, neg ): + # '%' is translated into '*' wildcard + cmd,sep,arg = arg.partition(' ') + arg = arg.replace('%','*') + arg = arg.strip() + + # special case for olc lumi + if cmd.lower() == 'olc' and 'lumi' in arg.lower(): + atlqarg = 'olclumi' + a = arg.split() + if len(a) == 2: + arg = a[1].strip() + else: + print 'ERROR in argument of command "olc lumi": no argument given' + sys.exit() + + # make equal to 'lumi' (for backward compatibility) + if cmd.lower() == 'lumi': + atlqarg = 'olclumi' + if not arg: + print 'ERROR in argument of command "olc lumi": no argument given' + sys.exit() + + # special case for 'lhc' + if cmd.lower() == 'lhc': + if 'stablebeams' in arg.lower(): + arg,sep,val = arg.partition(' ') + val = val.strip().upper() + if val == '1' or val == 'T': val = 'TRUE' + elif val == '0' or val == 'F': val = 'FALSE' + elif val != 'TRUE' and val != 'FALSE': + print 'ERROR in argument of command "lhc": "%s". Value must be boolean (true/false or 1/0)' % val + sys.exit() + arg += ' ' + val + + atlqarg = atlqarg.strip() + if atlqarg=='db' and (arg=='MC' or arg=='OFLP'): + self.isMCDB = True + + return '--%s "%s"' % (atlqarg, arg) + + def InterpretStreams( self, atlqarg, arg, neg ): + # '%' is translated into '*' wildcard + arg = arg.split()[1:] + retstr = "--" + atlqarg.strip() + ' "' + arg[0].replace('%','*') + if len(arg)>1: + return retstr + " " + self.replaceNumbers(arg[1]) + '"' + else: + return retstr + '"' + + def InterpretDuration( self, atlqarg, arg, neg ): + # sanity check + key, sep, arg = arg.partition(' ') + units = {'sec': 1, 's': 1, 'm': 60, 'h': 3600, 'd': 86400 } + found = False + for u, fac in units.items(): + if u in arg: + arg = arg.replace(u,'') + if '-' in arg: sign = '-' + else: sign = '+' + arg = "%i%s" % (int(arg.replace(sign,'').strip())*fac,sign) + found = True + if not found: + self.ParseError( "Unit missing in duration tag: '%s' - should be 's', 'm', 'h', or 'd'" % arg ) + + return self.InterpretRange( atlqarg, key + ' ' + arg, neg ) + + def InterpretMagnets( self, atlqarg, arg, neg ): + key, s, arg = arg.partition(' ') + + # if 'arg' is empty, intepret directly + if arg.strip() == '': + self.ParseError( 'Keyword "mag(net)" requires argument' ) + + # check if 'not' given + arg = arg.strip()[0] + + # now interpret + if arg == "" : newarg = "off" + else: + if arg == 's': newarg = "solenoid" + elif arg == 't': newarg = "toroid" + else: + self.ParseError( "cannot interpret of magnetic field: '%s'" % arg ) + if neg: newarg += "off" + else: newarg += "on" + + return "--" + atlqarg.strip() + ' "' + newarg.strip().replace(' ','') + '"' + + @classmethod + def DQGetFolder( cls, args, allowWildCards=False ): + """interpret dq show command + * args are all arguments after dq codeword + + possible formats for 'dq args': + 1) dq + 2) dq D + 3) dq F + 4) dq F# + 5) dq D F + 6) dq #T + 7) dq D #T + 8) dq F#T + 9) dq D F#T + + where + D is a komma-separated list of defects or DQ-flags or *, each optionally proceeded by a ! or followed by a \(defect,defect,defect,...) + F an folder name [default DEFECTS] + T a cool tag [default HEAD] + + If no folder is specified the new defects folder is used + """ + + + d = { 'flag': '', 'folder': '', 'tag': ''} + if '#' in args: + if allowWildCards: + m = re.match('(?P<flag>[!.*\w,\-\$\\\]*?)\s*?(?P<folder>\w*)#(?P<tag>[\w-]*)', args) + else: + m = re.match('(?P<flag>[!\w,\-\$\\\]*?)\s*?(?P<folder>\w*)#(?P<tag>[\w-]*)', args) + else: + m = re.match('(?P<flag>[!\w,\-\$\\\]*)\s*(?P<folder>[\w-]*)', args) + + if m: d.update(m.groupdict()) + + flag = d['flag'].lower() + folder = d['folder'].upper() + tag = d['tag'] + + if flag.upper() in cls.dqFolderList: + folder = flag.upper() + flag = '' + + if folder == '': folder = 'DEFECTS' + if flag == '': flag = '*' + if tag != '': + tag = '#' + tag + folder = folder + tag + + return flag, folder, tag + + + def ShowDQ( self, atlqarg, short, arg ): + + # check if 'arg' has additional arguments (eg, dq PIXB, ...) + args = arg.partition(' ')[2] + + # check/insert COOL folder + flags, folder, tag = self.DQGetFolder( args, allowWildCards = True ) + + newarg = "" + + if folder.startswith('DEFECTS'): + # defects DB + if flags == "*": # no additional arguments given --> show all flags + return '--show "dq DEFECTS%s" ' % tag + else: + # assume some defects are given, split by "," + return ' '.join(['--show "dq %s DEFECTS%s"' % (fl.upper(), tag) for fl in flags.split(',')]) + + # replace 'lar' by appropriate choices + flags = flags.replace( 'lar','emba,embc,emeca,emecc,heca,hecc,fcala,fcalc' ) + + # replace 'tile' by appropriate choices + flags = flags.replace( 'tile','tigb,tilba,tilbc,tieba,tiebc' ) + + # replace 'trig' by appropriate choices + flags = flags.replace( 'trig','l1cal,l1mub,l1mue,l1ctp,trcal,trbjt,trbph,trcos,trele,trgam,trjet,trmet,trmbi,trmuo,trtau,tridt' ) + + dqitems = DQChannels() + if flags == "*": # no additional arguments given --> show all flags + # query over all DQ channels and add to show + for key, dqchan in dqitems: + newarg += '--show "dq %s %s" ' % (dqchan, folder) + else: + # assume some DQ channels are given, split by "," or " " + argList = flags.split(',') + for a in argList: + a = a.strip().lower() + if self.isVirtualFlag(a): + newarg += '--show "dq %s %s" ' % (a.upper(), folder) + else: + for key, dqchan in dqitems: + dc = dqchan.lower() + if a[0:3]==('trg'): # all trigger dq (tr but not trt) + if dc[0:2]=="tr" and dc[2]!='t': + newarg += '--show "dq %s %s" ' % (dqchan, folder) + else: + if a in dc: + newarg += '--show "dq %s %s" ' % (dqchan, folder) + + return newarg + + + + def InterpretDQ( self, atlqarg, args, neg ): + # the following formats for dq need to work + # dq [any] sys1[,sys2] value[+|-] // note: a system must be given, if you want all the specify '*' e.g. 'dq * g' + + # split, and remove first word ('dq'), be reminded that a split automatically removes _all_ whitespaces + args = args.split()[1:] + + hasColor = len(args)>0 and re.match('(n.a.|u|r|y|g|b)[+-]{0,1}$',args[-1].lower()) + if hasColor: + # last argument must be dq value + dqflagVal = args[-1] + args = args[:-1] + else: + dqflagVal = 'x' + + + # check for the 'any' identifier (any --> OR, otherwise AND) + useAny = len(args)>0 and (args[0] == 'any') + if useAny: args = args[1:] + + # get the cool folders + flags, folder, tag = self.DQGetFolder( ' '.join(args) ) + + newarg = "" + + if folder.startswith('DEFECTS'): + if flags == "*": + newarg = "--dq !ANY " + folder + else: + # assume some DQ channels are given, split by "," or " " + dqchans = flags.upper().split(',') + if useAny: + newarg += self.InterpretSingleDQ( atlqarg, (','.join(dqchans), dqflagVal, folder) ) + ' ' + else: + for dqchan in dqchans: + newarg += self.InterpretSingleDQ( atlqarg, (dqchan, dqflagVal, folder) ) + ' ' + + else: + if not hasColor: + self.ParseError( 'DQ flags need to be assigned a value ("n.a.", "u", "g", "y", "r"), eg, "dq PIXB,TIL yellow+" or "dq g"' ) + + # treat some old aliases + flags = flags.replace( 'lar','emba,embc,emeca,emecc,heca,hecc,fcala,fcalc' ) + flags = flags.replace( 'tile','tigb,tilba,tilbc,tieba,tiebc' ) + flags = flags.replace( 'trig','l1cal,l1mub,l1mue,l1ctp,trcal,trbjt,trbph,trcos,trele,trgam,trjet,trmet,trmbi,trmuo,trtau,tridt' ) + + # loop over DQ channels + dqitems = DQChannels() + if flags == "*": # no additional arguments given --> select on all flags + # query over all DQ channels and add to show + dqchans = [y for (x,y) in dqitems] + if useAny: + newarg += self.InterpretSingleDQ( atlqarg, (','.join(dqchans), dqflagVal, folder) ) + ' ' + else: + for dqchan in dqchans: + newarg += self.InterpretSingleDQ( atlqarg, (dqchan, dqflagVal, folder) ) + ' ' + + else: + # assume some DQ channels are given, split by "," or " " + dqchans = [] + for a in flags.upper().split(','): + if self.isVirtualFlag(a): + tmpdqchans = [a] + else: + # wildcard -- DANGEROUS -- + tmpdqchans = [dqchan for (k,dqchan) in dqitems if a in dqchan] + if 'LUMI' == a and 'LUMIONL' in tmpdqchans: tmpdqchans.remove('LUMIONL') + if 'IDVX' == a and 'MMUIDVX' in tmpdqchans: tmpdqchans.remove('MMUIDVX') + if len(tmpdqchans)==0: self.ParseError( 'Unknown DQ channel: "%s"' % a ) + dqchans += tmpdqchans + + if useAny: + newarg += self.InterpretSingleDQ( atlqarg, (','.join(dqchans), dqflagVal, folder) ) + ' ' + else: + for dqchan in dqchans: + newarg += self.InterpretSingleDQ( atlqarg, (dqchan, dqflagVal, folder) ) + ' ' + + + return newarg + + def isVirtualFlag(self, flag): + return '_' in flag + + def InterpretSingleDQ( self, atlqarg, args ): + # expand colours + if len(args) != 3: + self.ParseError( 'ERROR in DQ argument: "%s" --> need <Flag> AND <Status> (len is: %i)' % (fullarg, len(arg)) ) + flag, col, folder = args + + flag = flag.upper() # upper case status flags + + col = col.lower() # lower case color status only + trcol = '' + if not folder.startswith('DEFECTS'): + if col[0] == 'n': trcol = 'n.a.' + elif col[0] == 'u': trcol = 'unknown' + elif col[0] == 'g': trcol = 'green' + elif col[0] == 'y': trcol = 'yellow' + elif col[0] == 'r': trcol = 'red' + elif col[0] == 'b': trcol = 'black' + else: + self.ParseError( 'ERROR in DQ argument: "%s" --> uknown status: %s' % (args, col) ) + if '+' in col: trcol+= '+' + if '-' in col: trcol+= '-' + else: + if col[0] == 'n': trcol = 'red' + elif col[0] == 'u': trcol = 'red' + elif col[0] == 'g': trcol = 'green' + elif col[0] == 'y': trcol = 'red' + elif col[0] == 'r': trcol = 'red' + elif col[0] == 'b': trcol = 'red' + elif col[0] == 'x': trcol = 'none' + else: + self.ParseError( 'ERROR in DQ argument: "%s" --> uknown status: %s' % (fullarg, col) ) + + + return "--" + atlqarg.strip() + ' "' + flag + ' ' + trcol + ' ' + folder + '"' + + def InterpretDetectorMask( self, atlqarg, arg, neg ): + arg = (arg.partition(' ')[2]).lower().strip() + anyflag = '' + if 'any' in arg: + arg = arg.replace('any','').strip() + if not neg: anyflag = 'A' + + # check who's in + mask = 0 + for sarg in arg.split(","): + for i in range(0,len(self.dName)): + # is vetoed ? + if i in self.vetoedbits: continue + + # sanity + d = self.dName[i].lower() + self.NotInAll[i] + if not ('unknown' in d or 'removed' in d): + # the actual query + if (sarg == 'all' and not 'NotInAll' in d) or sarg in d: + mask |= 1 << i + + # sanity check + if mask == 0: + print 'ERROR: could not find detector: "%s" in detector list' % arg + print self.dName + print 'Note: search is case INSENSITIVE' + self.ParseError( '' ) + + # decide whether detectors are required IN or OUT of partition + atlqarg.strip() + if neg: atlqarg += 'out' + else: atlqarg += 'in' + + return "--" + atlqarg + ' "%i%s' % (mask,anyflag) + '"' + + def ParseError( self, errtext, pos = -1 ): + print ' ' + print 'ERROR in argument: "%s"' % self.const_arg + print "%s" % errtext + print ' ' + sys.exit(1) + + def MatchingQueryArg(self, arg): + arg = arg.split()[0] # first word of arg 'r' in 'r 9000-23100' + for key in self.queryArgs: + short = self.queryArgs[key][0] # e.g. 'r(uns)' + shortNoPar = short.replace('(','').replace(')','') # e.g. 'runs' + if '(' in short: + short = short[:short.find('(')] # e.g. 'r' + matchesArg = shortNoPar.startswith(arg.lower()) and arg.lower().startswith(short) + if matchesArg: return key + return None + + def RetrieveQuery( self, findarg ): + # check that find part starts with f and remove the f(ind) + if findarg[0] != 'f': + self.ParseError( "Argument must begin with 'f(ind)'", 0 ) + findarg = findarg[findarg.index(' ')+1:] # remove first word 'f(ind)' + + + argList = [x.strip() for x in findarg.split(" and ")] + + newarg = "" + for arg in argList: + # check if negation + negation = False + if 'not' in arg: + arg = arg.replace("not","").strip() + negation = True + + key = self.MatchingQueryArg(arg) + if not key: + self.ParseError( "Could not find predefined keyword for arg: '%s' in 'find' block" % (arg) ) + else: + short, atlqarg, function = self.queryArgs[key][0:3] + newarg += function( atlqarg, arg, negation ) + ' ' + + return newarg + + def RetrieveShow( self, showarg ): + + #self.default_show = ["run","events","time"] + arglist = self.default_show + [a.strip() for a in showarg.split(' and ') if a.strip()!=''] # split at 'and' + + # show all + if 'all' in arglist: + idx = arglist.index('all') + arglist[idx:idx+1] = ['ready', 'lhc', 'trigk', 'rel', 'streams', 'det'] # slice it out + # show summary + if 'summary' in arglist: + arglist += ['dur', 'allev', 'str', 'dq' ] + # show lhc ... + if any([arg.startswith('lhc') for arg in arglist]): + arglist += ['olc'] + # show dqsum & dqplots + tmpArgList = [arg.startswith('dqsum') or arg.startswith('dqpl') for arg in arglist] + if any(tmpArgList): + idx = tmpArgList.index(True) + arglist[idx+1:idx+1] = [ 'ready','lumi', 'lhc stablebeams', 'dq', 'ptag', 'trigrates L1_EM30', 'trigrates L1_EM5'] + + # interpret argument list + newarg = "" + for arg in arglist: + key = self.MatchingQueryArg(arg) # check argument validity + if key == None: + self.ParseError( "Could not find predefined keyword for arg: '%s' in 'show' block" % (arg) ) + + # interpret argument and append to string + (short, atlqarg, dummy, function) = self.queryArgs[key][0:4] + newarg += function( atlqarg, short, arg ) + + return newarg + + + def ShowVariable( self, atlqarg, short, arg ): + return '--show ' + atlqarg + ' ' + + def ShowWithArg( self, atlqarg, short, arg ): + if len(arg.split())==1: + if atlqarg == 'olc' : return '--show olclumi --show olclbdata --show olcfillparams ' + elif atlqarg == 'luminosity': return '--show "lhc min" --show "%s 0" ' % (atlqarg) + else : return '--show %s ' % atlqarg + else: + if atlqarg == 'olc': + return '--show "olclumi %s" --show olcfillparams --show olclbdata ' % arg.partition(' ')[2] + elif atlqarg == 'luminosity': + return '--show "lhc min" --show "%s %s" ' % (atlqarg, arg.partition(' ')[2]) + else: + return '--show "%s %s" ' % (atlqarg, arg.partition(' ')[2]) + + def PostProcessShowArgs( self, showargs ): + + # remove special duplicate + if len(showargs.split('lhc')) >= 3: showargs = showargs.replace('--show "lhc min"','') + + # remove duplicates in show block + duples = ['--show %s' % d.strip() for d in showargs.split('--show') if d.strip()!=''] + uniques = reduce(lambda l, x: (x not in l and l.append(x)) or l, duples, []) + showargs = ' '.join(uniques) + + return showargs + + + def ParseArgument( self, const_arg ): + self.const_arg = const_arg + arg = const_arg.strip() + + # first separate the three parts ('/' is separator) and strip the spaces around each + findarg, showarg, otherarg = [x.strip() for x in (self.const_arg.split('/')+['',''])[0:3]] + + # check that show part starts with sh and remove the sh(ow) + if showarg!='': + if showarg[0:2] != 'sh': + self.ParseError( "Show block must begin with 'sh(ow)'", 0 ) + showarg = showarg[showarg.index(' ')+1:] # remove first word 'sh(ow)' + #showarg = ' and ' + showarg # and put ' and ' in front + else: + if ' sh ' in findarg or ' sho ' in findarg or ' show ' in findarg: + self.ParseError( "Show block must be separated by '/' from find condition'", 0 ) + + # retrieve the query and show arguments (and check for input errors) + queryarg = self.RetrieveQuery( findarg.strip() ) + shwarg = self.RetrieveShow ( (showarg).strip() ) + + # add 'lhc' to show if 'olclumi' query + # necessary because of stable beams information + if '--olclumi' in queryarg and not 'stablebeams' in queryarg: queryarg += '--lhc "stablebeams TRUE" ' + + shwarg = self.PostProcessShowArgs( shwarg ) + + + # interpret other arguments + nodef_flag = False + oargs = otherarg.split() + # always verbose for the time being... + #if not 'verbose' in queryarg: queryarg += ' --verbose' + extraargs = {'verbose' : '--verbose'} + idx = 0 + while idx < len(oargs): + oa = oargs[idx] + if oa == 'v': extraargs['verbose'] = '--verbose' + elif oa == 'noroot': extraargs['noroot'] = '--noroot' + elif oa == 'root': extraargs['root'] = '--root' + elif oa == 'nohtml': extraargs['nohtml'] = '--nohtml' + elif oa == 'html': extraargs['html'] = '--html' + elif oa == 'utc': extraargs['utc'] = '--utc' + elif oa == 'nogrl': extraargs['nogrl'] = '--nogrl' + elif oa == 'grl' or oa == 'xmlfile': extraargs['xmlfile'] = '--xmlfile MyLBCollection.xml:MyLBCollection' + elif oa == 'nodef' : nodef_flag = True + elif idx>0 and (oargs[idx-1]=='grl' or oargs[idx-1]=='xmlfile'): + extraargs['xmlfile'] = '--xmlfile %s' % oa + else: + print "Extra argument '%s' unknown. Exiting." % oa + sys.exit(1) + idx+=1 + + if 'verbose' in extraargs: + print "Parser: '%s'" % self.const_arg + print ' find argument: "%s"' % findarg.strip() + print ' show argument: "%s"' % showarg.strip() + print ' extra argument: "%s"' % otherarg.strip() + + # no defaults for MC + if self.isMCDB: nodef_flag = True + + # add default arguments + if not nodef_flag: + for key, argument, defaultvalue in [ (x[0],x[1][1],x[1][5]) for x in self.queryArgs.items()]: + if not argument in queryarg and defaultvalue != "": + queryarg += " --" + argument + ' "' + defaultvalue + '"' + + extraarg = ' '.join(extraargs.values()) + + fullarg = '%s %s %s' % (queryarg, shwarg, extraarg) + + return fullarg + + +# command line driver for convenience +if __name__=='__main__': + + from CoolRunQuery.AtlRunQueryParser import ArgumentParser + ap = ArgumentParser() + + if len(sys.argv) <= 1: + print 'No query argument given' + sys.exit(1) + + if sys.argv[1].lower() == 'detmask': + print 'Detector mask %s correspond to:\n%s' % (sys.argv[2], DecodeDetectorMask( int(sys.argv[2] ))) + sys.exit() + + if sys.argv[1].lower() == 'help': + ap.ParserUsage() + sys.exit(1) + + atlqueryarg = ap.ParseArgument( ' '.join(sys.argv[1:]) ) + print "\nAtlRunQuery.py %s\n" % atlqueryarg + diff --git a/Database/CoolRunQuery/python/AtlRunQueryQueryConfig.py b/Database/CoolRunQuery/python/AtlRunQueryQueryConfig.py new file mode 100644 index 00000000000..96d41484461 --- /dev/null +++ b/Database/CoolRunQuery/python/AtlRunQueryQueryConfig.py @@ -0,0 +1,24 @@ +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + +from utils.AtlRunQueryUtils import Enumerate + +import time + +class QC: + localtime = True + timezone = ('CET','CEST') + datapath = '' + + @classmethod + def tzdesc(cls): + return QC.timezone[1] if QC.localtime else 'UTC' + + @classmethod + def settimezone(cls): + time.tzname = QC.timezone + + @classmethod + def localStr(cls): + return "local" if QC.localtime else 'UTC' + + diff --git a/Database/CoolRunQuery/python/AtlRunQueryRun.py b/Database/CoolRunQuery/python/AtlRunQueryRun.py new file mode 100644 index 00000000000..d2de03b180f --- /dev/null +++ b/Database/CoolRunQuery/python/AtlRunQueryRun.py @@ -0,0 +1,2209 @@ +#!/bin/env python + +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration +# +# ---------------------------------------------------------------- +# Script : AtlRunQueryRun.py +# Project: AtlRunQuery +# Purpose: Library with the Run class +# Authors: Andreas Hoecker (CERN), Joerg Stelzer (DESY) +# Created: Feb 10, 2009 +# ---------------------------------------------------------------- +# +from __future__ import with_statement +from CoolRunQuery.utils.AtlRunQueryTimer import timer + +from utils.AtlRunQueryUtils import addKommaToNumber, filesize, prettyNumber, coolDbConn, RunPeriods +from utils.AtlRunQueryLookup import DecodeDetectorMask +from utils.AtlRunQueryIOV import IOVRange,IOVTime +from utils.AtlRunQueryLookup import DQChannelDict, LArConfig, isDQ, OLCAlgorithms +from output.AtlRunQueryRoot import makeLBPlot, makeLBPlotList, makeTimePlotList, makeLBPlotSummaryForLHC, makeBSPlots, SaveGraphsToFile +from AtlRunQueryQueryConfig import QC +from utils.AtlRunQueryMemUtil import memory +from selector.AtlRunQuerySelectorBase import DataKey + +import math +import time, calendar +import sys +import re +import datetime +import subprocess +import os.path +import urllib +from copy import deepcopy +from collections import defaultdict + +_fW = {'Run' : 12, 'NLB': 5, 'Time': 50, '#Events': 10, 'Stream': 10} + +class DataEntry: + def __init__(self, iov, value, reject=False): + self.iov = iov + self.lbrange = (self.iov.startTime.lb, self.iov.endTime.lb) # note that the end lb is not part of the iov + self.value = value + self.rejected = reject + + def __str__(self): + return "[%i-%i) %s %r" % (self.lbrange[0], self.lbrange[1], "r" if self.rejected else "a", self.value) + + def __repr__(self): + return "<DataEntry %s>" % self + + def __len__(self): + length_L = self.lastlb-self.startlb+1 + if length_L>0x7FFFFFFF: length_L = 0x7FFFFFFF + return int(length_L) + + @property + def startlb(self): + return self.lbrange[0] + + @property + def endlb(self): + return self.lbrange[1] + + @property + def lastlb(self): + return ((0x100000000 + self.endlb)-1) & 0xFFFFFFFF + + def contains(self, lb): + return self.lbrange[0]<=lb and lb<self.lbrange[1] + + def intersects(self,lbrange): + lbstart, lbend = lbrange + return self.contains(lbstart) or self.contains(lbend-1) or (lbstart<=self.lbrange[0] and self.lbrange[1]<lbend) + + def pickled(self): + v = self.value.pickled() if hasattr(self.value,'pickled') else self.value + return { 'firstlb' : self.startlb, 'lastlb' : self.lastlb, 'value' : v, 'accepted' : not self.rejected } + + +class DataEntryList(list): + def __init__(self, key, run): + super(DataEntryList,self).__init__() + self.key = key + self.run = run + + def atLB(self,lb): + for e in self: + return [x for x in self if x.contains(lb)] + return None + + def stops(self): + return set([e.startlb for e in self]).union(set([e.lastlb+1 for e in self])) + + @property + def lastlb(self): + return max([e.lastlb for e in self]) + + @property + def endlb(self): + return max([e.endlb for e in self]) + + def pickled(self): + return [e.pickled() for e in self] + + +class RunData: + + # this is a temporary thing to get the DQDefects working, I still want to change the way the selectors work + DQKeys = [] + DQLogic = [] + DefectSelector = None + + def __init__(self,run): + self.__run_o = run + self.run = run.runNr + self.data = {} + self.data_current_lb = {} + self.current_lb = 0 + self.stops = [] + + def __contains__(self,k): + if type(k)==DataKey: + return k in self.data or k.ResultKey in self.data + return k in self.data + + def __getitem__(self,k): + if not k in self: + self.data[k] = DataEntryList(key=k, run=self.run) + return self.data[k] + else: + if type(k)==DataKey: + return self.data[k] if k in self.data else self.data[k.ResultKey] + return self.data[k] + + def __setitem__(self,k,value): + self.data[k]=value + + def __iter__(self): + self.stops = self._calcStops() + for lb in self.stops: # the last stop is the LB after the last + if lb>self.__run_o.lastlb: break + self._setLB(lb) + yield self + + def keys(self): + return self.data.keys() + + @property + def runNrS(self): + return str(self.run) + + @property + def lb(self): + return self.current_lb + + def lbend(self,lb=None): + if lb==None: lb=self.current_lb + return self.stops[self.stops.index(lb)+1]-1 + + @property + def NrLBs(self): + return "%i" % max([el.lastlb for el in self.data.values()]) + + @property + def result(self): + return self.data_current_lb + + @property + def isRejectedCurrentLB(self): + #print "Checking Reject for LB",self.lb,"-",self.lbend() + need_dq_check = False + for k,s in self.data_current_lb.items(): + if k in RunData.DQKeys: + need_dq_check = True + continue # exclude DQ from this, since it gets its own treatment + if s==None: continue + for e in s: + if e.rejected: + return True + if need_dq_check: + if self.isDQRejected(): + return True + if self.isDQDefectRejected(): + return True + return False + + def isDQRejected(self): + for orGroup in RunData.DQLogic: + groupAccept = False + for k in orGroup: + if len(self.data_current_lb[k])==0: + passes = False + else: + passes = not self.data_current_lb[k][0].rejected + if passes: + groupAccept = True + break + if not groupAccept: + return True + return False + + def isDQDefectRejected(self): + if not 'DQ' in self.data_current_lb: return False + return not RunData.DefectSelector(self.data_current_lb['DQ']) + + + @property + def isRejected(self): + for s in self: + if not s.isRejectedCurrentLB: return False + return True + + + def isReady(self,lb=None): + if not 'Ready for physics' in self: + raise RuntimeError("No ready data available") + readydata = [x for x in self.data['Ready for physics'] if x.value=='1'] + if lb==None: + return readydata + if type(lb)==int: + for x in readydata: + if x.contains(lb): return True + return False + if type(lb)==tuple and len(lb)==2: + for x in readydata: + if x.intersects(lb): return True + return False + raise RuntimeError("Can't interpret lb to check isReady(lb=%r)" % lb) + + + def getLBRanges(self,activeOnly=False): + last_lb = self.__run_o.nr_lb + a = [(int(data.lb),not data.isRejectedCurrentLB) for data in self if data.lb<=last_lb] + #a = [(int(data.lb),not data.isRejectedCurrentLB) for data in self] + if not a: return [] + start,state = a[0] + ranges = [] + for x in a[:-1]: + if x[1] == state: continue + ranges += [(state, start, x[0])] + start,state = x + last_start,last_state = a[-1] + if last_state==state: + ranges += [(state, start, last_lb+1)] + else: + ranges += [(state, start, last_start)] + ranges += [(last_state, last_start, last_lb+1)] + if activeOnly: + return [x for x in ranges if x[0]==True] + else: + return ranges + + def _setLB(self,lb): + self.current_lb = lb + self.data_current_lb = dict([(k,entrylist.atLB(lb)) for k,entrylist in self.data.items()]) + + def _calcStops(self): + stops = set() + for entrylist in self.data.values(): + stops.update(entrylist.stops()) + return sorted(list(stops)) + + def addResult(self, iov, k, value, reject): + if not iov: + iov = IOVRange(runStart=self.run, lbStart=1, runEnd=self.run+1, lbEnd=0) + self[k].append(DataEntry(iov=iov, value=value, reject=reject)) + + def astxt(self): + s = "" + if Run.showrunnr: + s += "%*s %*s " % (4, self.lb, 4, self.lbend()) + s += ("r" if self.isRejectedCurrentLB else "a") + if Run.showtime: s += "%*s " % (_fW['Time'],'') + if Run.showduration: s += " " + for k in Run.SortedShowOrder(): + k = k.ResultKey + w = 10 + if k in _fW: w = _fW[k] + v="" + if self.data_current_lb[k]: + v = [x.value for x in self.data_current_lb[k]][0] + if v==None: v="" + if k == "#Events" or k[0:4] == "STR:" or k=="TriggerMenu" or k=="TriggerRates" or k=="olc:bcidmask": + v = "" + s += "%*s " % (w,v) + return s + + + + + +class Run: + ShowOrder = [] + _SortedShowOrder = None + DisplayOrder = map(re.compile,['#Events','Ready','lhc:|olc:', 'TriggerMenu|SMK|Release|L1 PSK|HLT PSK|BGS|TriggerRates', 'bs:', 'STR:', 'DQ|SHIFTOFL:|LBSUMM:', 'lar:', 'Detector','.*']) + PromptCalibRuns = [] + NemoTasks = {} + showrunnr = False + showtime = False + showduration = False + writehtml = False + bgcolor = "1" + totevents = [0, 0] + runnropen = -1 + showCAFLinks = False + Datapath = '' + Fieldinfo = {} + GlobalTooltips = [] + BeamspotSource = '' + runPeriods = RunPeriods() + TmpBoxContent = '' + + @classmethod + def AddToShowOrder(cls,key): + #print "ADDING TO SHOWORDER %r" % key + if not key in cls.ShowOrder: + cls.ShowOrder += [ key ] + + @classmethod + def addGlobalToolTip(cls, var, content): + cls.GlobalTooltips += ['dw_Tooltip.content_vars["%s"] = {content: \'%s\'};' % (var, content.replace("'","'"))] + + def __init__(self, runNr=0): + self.runNr = runNr + self.sor = 0 + self.eor = 0 + self.lastlb = 0 + self.selDataIncomplete = False + self.showDataIncomplete = False + self.xvecStb = [] + self.hasStableBeamsInfo = False + # the results of the query index by query key + self.result = {} + # the run statistics index by query key (filled in selector.runAfterQuery) + self.stats = {} + # the start times of all lumiblocks [start_LB1, start_LB2, start_LB3, ..., start_LBN] + self.lbtimes = [] + self.tooltips = [] + self.data = RunData(run=self) + # luminosity unil + self.lumiunit = 1.0 # default unit + self.lumiunittxt = 'nb' + self.instlumiunittxt = '30' + self.givepeakmuinfo = True + if self.runNr > 168206 and self.runNr <= 170482: + self.lumiunit = 1.0e6 # unit is mbarn for heavy ions + self.lumiunittxt = 'mb' + self.instlumiunittxt = '24' + self.givepeakmuinfo = False + + + @property + def runNrS(self): + return "%i" % self.runNr + + @property + def data_current_lb(self): + return self.data.data_current_lb + + @property + def NrLBs(self): + return "%i" % self.lastlb + + @property + def nr_lb(self): + return self.lastlb + + def addToolTip(self, var, content): + self.tooltips += ['dw_Tooltip.content_vars["%s\"] = {content: \'%s\'};' % (var, content.replace("'","'"))] + + def getStableBeamsVector(self): + if len(self.xvecStb)==0 and 'lhc:stablebeams' in Run.ShowOrder and 'lhc:stablebeams' in self.data: + entries = self.data['lhc:stablebeams'] + for entry in entries: + if entry.startlb == 0: continue + if 'true' in entry.value.lower(): + self.xvecStb += range(entry.startlb,entry.endlb) + self.hasStableBeamsInfo = len(self.xvecStb)>0 + return self.hasStableBeamsInfo, self.xvecStb + + @classmethod + def SortedShowOrder(cls): + if Run._SortedShowOrder != None: + if len(Run._SortedShowOrder) != len(Run.ShowOrder): + raise RuntimeError, "Sorting not up-to-date %i %i" % (len(Run._SortedShowOrder), len(Run.ShowOrder) ) + return Run._SortedShowOrder + hk = [] + for order in Run.DisplayOrder: + for x in Run.ShowOrder: + if not order.match(x.ResultKey): continue + if x in hk: continue + hk += [x] + Run._SortedShowOrder = hk + if len(Run._SortedShowOrder) != len(Run.ShowOrder): + raise RuntimeError, "Sorting not up-to-date after creation %i %i" % (len(Run._SortedShowOrder), len(Run.ShowOrder) ) + return Run._SortedShowOrder + + @classmethod + def headerkeys(cls): + hk = [] + if Run.showrunnr: hk += ['Run','Links','#LB'] + if Run.showtime: hk += ['Start and endtime (%s)' % QC.localStr()] + if Run.showduration: hk += ['Duration'] + for x in cls.SortedShowOrder(): + x = x.ResultKey + if 'L1 PSK' in x or ('olc:' in x and ('beam1bunches' in x or 'beam2bunches' in x or 'collbunches' in x or 'beam1intensity' in x or 'beam2intensity' in x)): continue + hk += [x] + return hk + + + @classmethod + def header(cls): + if Run.writehtml: + # flags... + hasLAr = False + hasLHC = False + hasBS = False + ofllumiFlag = False + s = '<tr>\n' + if Run.showrunnr: s += ' <th>Run</th><th>Links</th><th>#LB</th>\n' + if Run.showtime: + s += ' <th>Start and endtime (%s)</th>\n' % QC.localStr() + if Run.showduration: s += ' <th>Duration</th>\n' + for ik, data_key in enumerate(Run.SortedShowOrder()): + k = data_key.ResultKey + if k[0:4] == 'STR:': s += ' <th><font size="-2">%s_<BR>%s</font></th>' % tuple(k[4:].split('_',1)) + elif k[0:8] == "Detector": s += ' <th>%s</th>' % k + elif "SolCurrent" in k: s += ' <th>Solenoid<br>current (A)</th>' + elif "TorCurrent" in k: s += ' <th>Toroid<br>current (A)</th>' + elif 'DQ' == k: + s += ' ' + matching_names = dict(Run.Fieldinfo['DQ']['DefMatch']) + first = True + for channelname in Run.Fieldinfo['DQ']['DefChannels']: + tip = "defect_match_%s" % channelname + info = "%s # %s" % (channelname if channelname!="" else "*",data_key._second_internal_key if data_key._second_internal_key!="" else "HEAD") + s += '<th style="align:center; white-space:nowrap; padding-right: 10px; padding-left: 10px; ">' + s += 'Data Quality<br><span class="showTip %s tooltip" style="font-weight:normal; font-size:80%%">(%s)</span>' % (tip, info) + if first: + s += '<div style="font-size: xx-small; cursor: pointer;" onclick="toggle_dq(this)">[show tolerable]</div>' + first = False + s += '</th>' + + a = sorted(matching_names[channelname]) + #horizontal + #slices = [slice(x,len(a)+3,4) for x in range(4)] + #zipped = zip(*map((a+4*['']).__getitem__,slices)) + #vertical + ncol=4 + n = 4*[len(a)/4] + for x in range(len(a)-4*(len(a)/4)): + n[x]+=1 + + cx = 0 + slices = [] + for x in n: + slices += [slice(cx,cx+x)] + cx+=x + m = map(a.__getitem__,slices) + for x in range(1,len(m)): m[x]+=[''] + zipped = zip(*m) + + tts = '' + for z in zipped: + tts += '<tr>%s</tr>' % ''.join(["<td>%s</td>"% x for x in z]) + content = '<table style="width:500px; font-size:80%%;">%s</table>' % tts + Run.addGlobalToolTip(tip,content) + + elif isDQ(k): + foldertag,flag = k.split(':') + + if flag in Run.Fieldinfo: + tipname = 'dqvfdesc_%s' % flag + commentbox = '%s' % (Run.Fieldinfo[flag]) + Run.addGlobalToolTip(tipname, commentbox) + s += '<th class="showTip %s tooltip" style="cursor:pointer">' % tipname + else: + s += ' <th>' + + s += '<font size="-1">%s</font><BR><font style="font-weight:normal;font-size:70%%">' % flag + if '#' in foldertag: + s += '(%s #<br>%s)' % tuple(foldertag.split('#',1)) + else: + s += '(%s)' % (foldertag) + s += '</font></th>' + + elif k == "L1 PSK": continue + elif k == "HLT PSK": s += ' <th>Prescale keys</th>' + elif k == "Release": s += ' <th>HLT Release</th>' + elif k == "Datasets": s += ' <th>Tier-0 Datasets</th>' + elif k == '#Events (incl. calib.)': s += ' <th>#Events<br><font size="-2">(incl. calib.)</font></th>' + elif k == '#Events (streamed)' : s += ' <th>#Events<br><font size="-2">(streamed)</font></th>' + elif 'lar:' in k: + hasLAr = True + if 'runtype' in k: s += ' <th><font size="-2">Run type</font></th>' + elif 'nsamples' in k: s += ' <th><font size="-2">#Samples</font></th>' + elif 'firstsample' in k: s += ' <th><font size="-2">1st sample</font></th>' + elif 'format' in k: s += ' <th><font size="-2">Format</font></th>' + else: + print 'ERROR: unknown LAr option "%s"' % k + sys.exit(1) + elif 'lhc:' in k: + if 'fillnumber' in k: s += ' <th> LHC Fill</th>' + elif 'stablebeams' in k: + s += ' <th>Stable beams</th>' + if 'lhc:beammode' in Run.ShowOrder: s += ' <th>Beam mode</th>' + if 'lhc:beamenergy' in Run.ShowOrder: + s += ' <th>Beam energy and intensities</th>' + hasLHC = True + + elif 'beamenergy' in k: continue # included in 'fillbeams' summary + elif 'nbunch1' in k: s += ' <th>#Bunches B1 <br><font size="-2"><b>(NOT YET RELIABLE)</b></font></th>' + elif 'nbunch2' in k: s += ' <th>#Bunches B2 <br><font size="-2"><b>(NOT YET RELIABLE)</b></font></th>' + elif 'nbunchcoll' in k: s += ' <th>#Bunches<br>colliding <br><font size="-2"><b>(NOT YET RELIABLE)</b></font></th>' + elif 'beamtype1' in k: s += ' <th>Beam type B1</th>' + elif 'beamtype2' in k: s += ' <th>Beam type B2</th>' + elif 'machinemode' in k: s += ' <th>LHC mode</th>' + elif 'beammode' in k: continue + else: + print 'ERROR: unknown LHC option "%s"' % k + sys.exit(1) + elif 'ofllumi:' in k: + if not ofllumiFlag: + s += ' <th>Offline Luminosity<br><font style="font-weight:normal">(%s)</font></font></th>' % k.split(':')[-1] + ofllumiFlag = True + elif 'bs:' in k: + hasBS = True + kt = k.replace('bs:','') + if 'bs:Pos' in k or 'bs:Sig' in k: + s += ' <th><font size="-2">%s<br><font style="font-weight:normal">(mm)</font></font></th>' % kt + elif 'bs:Tilt' in k: + s += ' <th><font size="-2">%s<br><font style="font-weight:normal">(rad)</font></font></th>' % kt + else: + s += ' <th><font size="-2">%s</font></th>' % kt + elif 'BPM' in k: s += ' <th>Beam Position Monitors (BPM)</th>' + elif 'olc:' in k: + if 'olc:lumi' in k: + tp1, tp2, chan = k.split(':') + try: + chan = float(chan) + except ValueError: + chan = -1 + if OLCAlgorithms.has_key(chan): chanstr = OLCAlgorithms[chan] + else: chanstr = 'UNKNOWN' + s += ' <th>Online del. Luminosity <font size="-2"><br><font style="font-weight:normal">[%s]</font></font></th>' % chanstr + elif 'beam1bunches' in k: continue # not used anymore + elif 'beam2bunches' in k: continue # not used anymore + elif 'collbunches' in k: continue # not used anymore + elif 'bcidmask' in k: s += ' <th>Bunch structure</th>' + elif 'beam1intensity' in k: continue # included in 'fillbeams' summary + elif 'beam2intensity' in k: continue # included in 'fillbeams' summary + else: + s += ' <th>%s</th>' % k + + elif 'BGS Key' in k: s += ' <th>Bunch group key</th>' + + else: + s += ' <th>%s</th>' % k + s += '\n' + s += '</tr>' + + # generate a second header line + secondheader = '' + patterns = [ ('lar:', 'LAr configuration'), + ('lhc:|olc:', 'LHC and online luminosity information' if any(['olc:' in x for x in Run.headerkeys()]) else 'LHC information' ), + ('bs:', 'Beam spot parameters (%s)' % Run.BeamspotSource), + ('STR:', 'Data stream statistics'), + ('TriggerMenu|SMK|Release|L1 PSK|HLT PSK|BGS|TriggerRates', 'Trigger information'), + ('SHIFTOFL:', 'Data quality (SHIFTOFL)'), + ('LBSUMM:', 'Data quality (LBSUMM)') + ] + + order = [] + for (p,hdesc) in patterns: + matchedpositions = [idx for (idx,head) in enumerate(Run.headerkeys()) if re.match(p,head)] + #print "pattern",p,matchedpositions + if matchedpositions: order += [(min(matchedpositions),max(matchedpositions),hdesc)] + order.sort() + + mergeempty = True + if len(order)>0: + current=0 + for th in order: + if mergeempty: + if th[0]>current: + secondheader += '<th colspan="%s"></th>' % (th[0]-current) + else: + for x in xrange(th[0]-current): secondheader += '<th></th>' + secondheader += '<th colspan="%s">%s</th>' % (th[1]-th[0]+1,th[2]) + current=th[1]+1 + if len(Run.headerkeys())>current: + if mergeempty: + secondheader += '<th colspan="%s"></th>' % (len(Run.headerkeys())-current) + else: + for x in xrange(len(Run.headerkeys())-current): secondheader += '<th></th>' + secondheader = "<tr>%s</tr>" % secondheader + + + s = '<thead>' + secondheader + s + '</thead>\n' + + # Global (run independent) tooltips + + # OLC + if any([k for k in Run.ShowOrder if "olc:lumi" in k.ResultKey]): + boxcontent = '<font color="#AA0000">Click to enlarge figure and to obtain online integrated luminosity versus LB</font>' + Run.addGlobalToolTip("OLCLumi", boxcontent) + + # OFLLumi + if any([k for k in Run.ShowOrder if "ofllumi" in k.ResultKey]): + boxcontent = '<font color="#AA0000">Click to obtain offline integrated luminosity versus LB</font>' + Run.addGlobalToolTip("OFLLumi", boxcontent) + + # OFLBS - beamspot + if any([k for k in Run.ShowOrder if "bs:" in k.ResultKey]): + boxcontent = '<font color="#AA0000">Click to obtain %s beamspot versus LB</font>' % (Run.BeamspotSource.split()[0]) + Run.addGlobalToolTip("OFLBS", boxcontent) + + # BPM info + if any([k for k in Run.ShowOrder if "BPM" == k.ResultKey]): + boxcontent = '<font color="#AA0000">Click to enlarge figure</font>' + Run.addGlobalToolTip("BPM", boxcontent) + + # LHC Summary + if any([k for k in Run.ShowOrder if "lhc:fillnumber" in k.ResultKey]): + boxcontent = '<font color="#AA0000">Click to enlarge figure</font>' + Run.addGlobalToolTip("LHCSummary", boxcontent) + + else: + s = '' + for l in [1,2]: + if Run.showrunnr: + if l==0: s += '%-*s %*s ' % (_fW['Run'],'Run',_fW['NLB'],'#LB') + if l==1: s += '%-*s %*s ' % (_fW['Run'],' ',_fW['NLB'],' ') + s+=" " # accept/reject flag + if Run.showtime: + hstr = 'Start and endtime (%s)' % QC.localStr() + if l==0: + s += '%*s ' %(_fW['Time'],hstr) + if l==1: s += '%*s ' %(_fW['Time'],' ') + if Run.showduration: + if l==0: s += '%*s ' % (10,'Duration') + if l==1: s += '%*s ' % (10,' ') + for k in Run.SortedShowOrder(): + if k.Type == DataKey.STREAM: + w = max(len(k.Header)-k.Header.find('_'),_fW['Stream']) + if l==0: s += '%*s ' % (w,k.Header[4:k.Header.find('_')]) + if l==1: s += '%*s ' % (w,k.Header[k.Header.find('_')+1:]) + else: + w = 10 + if k in _fW: w = _fW[k.ResultKey] + if l==0: s += '%*s ' % (w,k.Header) + if l==1: s += '%*s ' % (w,' ') + if l==0: s+='\n' + return s + + def addResult(self, resDictKey, value, iov=None, reject=False): + if resDictKey=='DQ' and value=='n.a.': return + #print "Run.addResult:",resDictKey, value, iov,"reject=",reject + if not resDictKey in self.result: + self.result[resDictKey] = value + if iov: + if iov.startTime.lb>self.lastlb: return # sometimes there are IOVs completely outside the run e.g. run 165821 DQ SHIFTOFL + iov.endTime = min(iov.endTime, IOVTime(self.runNr,self.lastlb+1) ) + self.data.addResult(iov, resDictKey, value, reject) + + def runlinks(self): + s = '' + # DS + if time.gmtime(self.sor/1.E9).tm_year >= 2010: + s += ' <a href="http://atlas.web.cern.ch/Atlas/GROUPS/DATAPREPARATION/DataSummary/%i/run.py?run=%i" target="_blank" title="Data summary information for run %i"><font size="-3">DS</font></a>, \n' % (time.gmtime(self.sor/1.E9).tm_year, self.runNr, self.runNr) + # RS + s += ' <a href="http://atlas-service-db-runlist.web.cern.ch/atlas-service-db-runlist/php/runDetails.php?run=%i" target="_blank" title="Run summary information for run %i"><font size="-3">RS</font></a>, \n' % (self.runNr, self.runNr) + # BS + s += ' <a href="https://atlas-beamspot.cern.ch/webapp/jobs/?r=%i" target="_blank" title="Beam spot fit information for run %i"><font size="-3">BS</font></a>, \n' % (self.runNr, self.runNr) + # AMI + s += ' <a href="https://ami.in2p3.fr/AMI/servlet/net.hep.atlas.Database.Bookkeeping.AMI.Servlet.Command?Converter=/AMIXmlToAMIProdHtml.xsl&Command=FormBrowseDatasetPerRun+-runNumber=%i" target="_blank" title="AMI reference for run %i"><font size="-3">AMI</font></a>, \n' % (self.runNr, self.runNr) + # DQ + s += ' <a href="http://atlasdqm.web.cern.ch/atlasdqm/DQBrowser/makeMatrix.php?selection=All&run=%g&runup=&dbinstance=COMP200_SHIFTOFL&tag=HEAD" target="_blank" title="Browse detailed data quality entries for run %i (uses SHIFTOFL folder in COMP200 conditions database)"><font size="-3">DQ</font></a>,<br>\n' % (self.runNr, self.runNr) + # ELOG + s += ' <a href="https://atlasop.cern.ch/elog/ATLAS/ATLAS/?mode=full&reverse=0&reverse=1&npp=20&ma=1&ya=2008&last=3064&subtext=%i&sall=0" target="_blank" title="Search for ELOG entries on run %i"><font size="-3">ELOG</font></a>, \n' % (self.runNr, self.runNr) + # DCS reference, format: http://atlas-dcs.cern.ch/navigation.php?date2=2009-07-26-10-12 + tbeg = time.strftime("%Y-%m-%d-%H-%M-%S", time.gmtime(self.sor/1.E9)) + t = time.gmtime(self.eor/1.E9) + tend = "%4i-%02i-%02i-%02i-%02i" % (t[0], t[1], t[2], t[3], t[4]) + s += ' <a href="http://atlas-dcs.cern.ch/navigation.php?date2=%s" target="_blank" title="DCS status at begin of run %i"><font size="-3">DCS:SoR</font></a>/' % (tbeg, self.runNr) + s += '<a href="http://atlas-dcs.cern.ch/navigation.php?date2=%s" target="_blank" title="DCS status at end of run %i"><font size="-3">EoR</font></a>, \n ' % (tend, self.runNr) + # OKS + # protect against missing OKS information + if self.data['oks']: + s += ' <a href="http://cern.ch/atlas-project-tdaq-cc/cgi/getdata.sh?tdaq-03-00-01&oracle://atlas_oks/r&atlas_oks_archive&%i&%i&ATLAS" target="_blank" title="Download online configuration for run %i (slow download!)"><font size="-3">OKS</font></a>\n' % ( self.data['oks'][0].value + (self.runNr,) ) + # lumi summary + # typical address: https://atlas.web.cern.ch/Atlas/GROUPS/DATAPREPARATION/RunSummary/run142308_summary.html + # OLD: fname = 'https://atlas.web.cern.ch/Atlas/GROUPS/DATAPREPARATION/RunSummary/run%i_summary.html' % (self.runNr) + fname = 'http://atlas.web.cern.ch/Atlas/GROUPS/DATAPREPARATION/DataSummary/runsum.py?run=%i\n' % (self.runNr) + fwget = urllib.urlopen(fname) + + wincontent = '' + for line in fwget: + if '</head>' in line and '<base href' not in wincontent: + wincontent += '<base href="http://atlas.web.cern.ch/Atlas/GROUPS/DATAPREPARATION/DataSummary/2010/"></base>' + wincontent += line + wincontent = wincontent.replace('href="css/atlas-datasummary.css"', 'href="http://atlas.web.cern.ch/Atlas/GROUPS/DATAPREPARATION/DataSummary/css/atlas-datasummary.css"') + + # remove temporarily second plot with integration + wincontent = wincontent.replace( '<td>\n<a href="rundata/run%i/run%i_ilumr.png"><img src="rundata/run%i/run%i_ilumr.png" alt="InstLumi" style="width: 200px; "></a></td>' % (self.runNr,self.runNr,self.runNr,self.runNr),'') + wincontent = wincontent.replace('"','"') + wincontent = wincontent.replace('./RunSummary/figs','https://atlas.web.cern.ch/Atlas/GROUPS/DATAPREPARATION/RunSummary/figs') + wincontent = wincontent.replace('<a href','<a target="_blank" href') + wincontent = wincontent.replace('width: 200px','width: 350px') + wincontent = wincontent.replace('width: 250px','width: 350px') + + + openwincmd = 'javascript:openLumiWindow(' + openwincmd += "'Print','<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html xmlns:"my"><head><title>Luminosity information for run %i</title></head><body style="background-color:#ffffff">" % self.runNr + openwincmd += wincontent + openwincmd += '</body></html>' + openwincmd += "')" + + return s + + + def timestr(self,format='html',closed=True): + """ sor and eor is always in UTC, so sor=0 means the run started 1.1.1970 at 00:00:00""" + if QC.localtime: + begin = time.localtime(self.sor/1.E9) + end = time.localtime(self.eor/1.E9) + else: + begin = time.gmtime(self.sor/1.E9) + end = time.gmtime(self.eor/1.E9) + + # begin string + beginstr = time.strftime("%a %b %d %Y %X",begin) + + # end format + endformat = "%X" + if end[0]>begin[0]: endformat = "%a %b %d, %Y %X" + elif end[1]>begin[1] or end[2]>begin[2]: endformat = "%a %b %d, %X" + + # end string + if closed: endstr = time.strftime(endformat,end) + else: endstr = '<font color=#BB0000>ongoing</font>' if format=='html' else 'ongoing' + + # output + if format=='html': + return '%s − %s' % (beginstr,endstr) + elif format=='seconds': #mainly for dictionary + return '%12.3f, %12.3f' % (self.sor/1.E9, self.eor/1.E9) + elif format=='secondsNum': #mainly for dictionary + return self.sor/1.E9, self.eor/1.E9 + else: + return '%s - %s' % (beginstr,endstr) + + def durationstr(self): + dt = time.gmtime((self.eor-self.sor)/1.E9)[2:6] + if dt[0]>1: + return "%id %ih %im %is" % ((dt[0]-1,) + dt[1:]) + else: + return "%ih %im %is" % dt[1:] + + @classmethod + def prettyChain(cls,tr,ps): + i = len(ps)-1 + while i>0: + if ps[i]==ps[i-1]: ps[i]=' ' + elif ps[i]<0: ps[i]='x' + else: ps[i]=str(ps[i]) + i-=1 + if ps[0]==None: ps[0]='n.a.' + elif ps[0]<0: ps[0]='x' + else: ps[0]=str(ps[0]) + + pss = '-'.join(ps) + s = "%s (%s)" % (tr.name,pss) + return s + + def splitTriggerChains(self, chainlist): + res = { 'L1': [], 'L2': [], 'EF': [] } + for tr,pslist in chainlist.items(): + if not tr.forshow: continue + k = tr.name[0:2] + res[k] += [Run.prettyChain(tr,pslist)] + res['L1'].sort() + res['L2'].sort() + res['EF'].sort() + l1chains = '<br> '.join(res['L1']) + l2chains = '<br> '.join(res['L2']) + efchains = '<br> '.join(res['EF']) + ret = () + if l1chains: ret += (l1chains.replace('.0',''), ) + if l2chains: ret += (l2chains.replace('.0',''), ) + if efchains: ret += (efchains.replace('.0',''), ) + return ret + + def __str__(self): + if Run.writehtml: + s = ashtml(self) + return s + else: + s = self.astxt() + for lbr in self.data: + s+= "\n" + lbr.astxt() + return s + + def __cmp__(self,other): + if isinstance(other,Run): + return self.runNr - other.runNr + return self.runNr - other + + + def astxt(self): + s = "" + if Run.showrunnr: + s += "run %*s %*s " % (_fW['Run']-4,self.runNrS, _fW['NLB'], self.NrLBs) + s += " " + + if Run.showtime: + s += "%*s " % (_fW['Time'],self.timestr('txt')) + + if Run.showduration: + dt = time.gmtime((self.eor-self.sor)/1.E9)[2:6] + if dt[0]>1: s += "%id %ih %im %is " % ((dt[0]-1,) + dt[1:]) + else: s += "%2ih %2im %2is " % dt[1:] + + for k in Run.SortedShowOrder(): + w = 10 + if k in _fW: w = _fW[k] + v = self.result[k.ResultKey] + ostr=v + if k == "#Events": + ostr = addKommaToNumber(v) + elif k.Type == DataKey.STREAM: + w=max(len(k.Header)-k.Header.find('_'),_fW['Stream']) + if isinstance(ostr,tuple): + ostr = ostr[0] + elif k=="TriggerMenu": + pv = [Run.prettyChain(tr,pslist) for (tr,pslist) in v.items() if tr.forshow] + ostr = ", ".join(pv) + elif k=="TriggerRates": + ostr = "NOT YET" + elif k=="olc:bcidmask": + ostr = "NOT YET" + + s += "%*s " % (w,ostr) + + return s + + + +def ashtml(run): + import CoolRunQuery.html.AtlRunQueryHtmlUtils as HU + + with timer("print runnur, runtime, etc"): + s = "<!-- Run %s -->\n" % run.runNrS + + # disctionary with results + if Run.bgcolor == "1": Run.bgcolor = "2" + else: Run.bgcolor = "1" + color = Run.bgcolor + + runrowclass = "out" + if run.showDataIncomplete: runrowclass="showmiss" + if run.selDataIncomplete: runrowclass="selmiss" + runrowclass += color + + s += '<tr class="out%s">\n' % (color) + + if Run.showrunnr: # show columns Run, Links, LB + # run number + if run.runNr == Run.runnropen: # is the run still ongoing? + s += ' <td style="text-align:center" class="%s"><font color="#BB0000">%s<br><font size="-2">(ongoing)</font></font>' % (runrowclass, run.runNrS) + else: + s += '<td style="text-align:center" class="%s">%s' % (runrowclass, run.runNrS) + p = Run.runPeriods.findPeriod( run.runNr ) + if p: + # split in the middle + idx=-1 + for i in range(p.count(',')/2+1): idx = p.find(',',idx+1) + s += '<br><font color="#488AC7"><font size=-2><nobr>Period: %s<br>%s</nobr></font></font>' % (p[:idx],p[idx+1:]) + + # open for prompt calibration? + if run.runNr in Run.PromptCalibRuns: + s += '<br><font color="#008800"><font size="-2">(in calib loop)</font></font>' + s += '</td>' + + # links to other information + s += '\n <td>\n%s</td>' % run.runlinks() + + # luminosity block and average lb duration + lbcontent = '%s' % run.NrLBs + try: + vf = float(run.NrLBs) + if vf > 0: lbcontent = '%s<br><font size="-2">(%i s)</font>' % (run.NrLBs, (run.eor-run.sor)/(1.0E9*vf) ) + except ValueError: pass + + window = HU.OpenWindow( HU.CreateLBTable(run), extracss=['html/atlas-runquery-lb.css'] ) + + # put into html page + tip = 'showTip LBStart_%i' % run.runNr + s += '\n <!-- LB Content -->\n' + s += ' <td style="text-align: right"><div class="%s tooltip" style="display:inline;cursor:pointer">%s%s</a></div></td>\n' % (tip, window, lbcontent) + + + if Run.showtime: + s += ' <td>%s</td>' % run.timestr('html',run.runNr != Run.runnropen) # is the run still ongoing? + + + if Run.showduration: + if run.runNr == Run.runnropen: s += ' <td><font color="#BB0000">%s<br><font size="-2">(ongoing)</font></font></td>' % run.durationstr() + else: s += " <td>%s</td>" % run.durationstr() + + # find maximum output rate in case of streams + sumstrnev = 0 + for k in Run.SortedShowOrder(): + if k.Type == DataKey.STREAM and k.ResultKey in run.stats and not 'calibration' in k.ResultKey: sumstrnev += run.result[k.ResultKey][0] + if sumstrnev == 0: sumstrnev = -1 + + # flags... + ofllumiFlag = False + + + # the loop + with timer("print keys"): + for data_key in Run.SortedShowOrder(): + k = data_key.ResultKey + v = run.result[k] + + with timer("keybuild %s" % k): + if any(['olc:beam2intensity' in k, + 'olc:beam1intensity' in k, + 'lhc:beamenergy' in k, + 'beam1bunches' in k, + 'beam2bunches' in k, + 'collbunches' in k, + 'L1 PSK' == k, + ]): continue + + + + s += '\n ' + + # ----------------------------------------------------------------------------------------------------------------------- + + if k == "#Events": + # compute approximative output rate in Hz + durInSec = (run.eor-run.sor)/1.0E9 + rate = 'n.a. Hz' + try: + if durInSec > 0: rate = '%.1f Hz' % (float(v)/durInSec) + except ValueError: # if n.a. + pass + s += ' <td style="text-align: right">%s<BR><font size="-2">(%s)</font></td>' % (addKommaToNumber(v),rate) + continue + + # ----------------------------------------------------------------------------------------------------------------------- + + if k == "#L1A" or k == '#L2A' or k == '#EFA' or k == '#Events (streamed)' or k == '#Events (incl. calib.)': + s += ' <td style="text-align: right">%s</td>' % addKommaToNumber(v) + continue + + # ----------------------------------------------------------------------------------------------------------------------- + + if k == 'DQ': # DEFECT + intolerable = Run.Fieldinfo['DQ']['IntolerableDefects'] + + for channelname in Run.Fieldinfo['DQ']['DefChannels']: + if channelname == '': + matching_dqentries = run.stats[k]["defects"] + elif channelname == 'DET': + matching_dqentries = [dqentry for dqentry in run.stats[k]["defects"] if not '_' in dqentry[0]] + else: + from re import compile + pattern = compile(channelname) + matching_dqentries = [dqentry for dqentry in run.stats[k]["defects"] if pattern.match(dqentry[0])] + + + # reduce to LB ranges inside READY + matching_dqentries_ready = [] + for (defect, listLBranges) in matching_dqentries: # dqentry is of format ('SCT_GLOBAL_STANDBY', [(1L, 49L), (119L, 123L)]) + readyLBRanges = [lbrange for lbrange in listLBranges if run.data.isReady(lbrange)] + if len(readyLBRanges)>0: + matching_dqentries_ready += [ (defect, readyLBRanges) ] + + + if len(matching_dqentries_ready)==0: + dq_info = "<table class='dqdefects' style='border-color: limegreen; height=100%'><tr><td> </td></tr></table>\n" + else: + dq_info = "<table class='dqdefects'>\n" + for defect,listLBranges in matching_dqentries_ready: + tip = 'showTip dqdefect_%i_%s tooltip' % (run.runNr, defect) + if defect in intolerable: + dq_info += "<tr class='%s'><td class='intolerable'>%s</td><td class='lb'>" % (tip, defect) + else: + dq_info += "<tr class='%s tolerable' style='visibility:collapse;'><td>%s</td><td class='lb'>" % (tip, defect) + dq_info += ", ".join([("%g−%g" % (x[0],x[1]-1) if x[1]-x[0]!=1 else "%g" % x[0]) for x in listLBranges]) + dq_info += "</td></tr>\n" + dq_info += '<tr height="100%%"><td height="100%%"></td></tr>\n' + dq_info += "</table>\n" + s += '<td class="def">%s</td>' % dq_info + + continue + + # ----------------------------------------------------------------------------------------------------------------------- + + if isDQ(k): # old, traffic-light DQ + extraDQInfo = '' + tip = '' + dqmax = run.stats[k]["max"] + n = run.stats[k]["counts"] + + foundComment = False + for (dq,comment), start, end in run.stats[k]["blocks"]: + dqcss = dq + if dq == 'n.a.': dqcss = 'NA' + elif comment: foundComment = True + + if n[dq]>0 and dq != dqmax: + if start == end-1: # only one LB + extraDQInfo += '<tr><td class="td%s" style="min-width: 45px; text-align: left; border-width: 0px; padding:1px;"> LB %g:</td><td class="td%s" style="text-align: left; border-width: 0px; padding: 1px; ">%s </td></tr>' % (dqcss,start,dqcss,dq) + else: # range of LBs + extraDQInfo += '<tr><td class="td%s" style="min-width: 45px; text-align: left; border-width: 0px; padding:1px;"> LB %g−%g:</td><td class="td%s" style="text-align: left; border-width: 0px; padding: 1px; ">%s </td></tr>' % (dqcss,start,end-1,dqcss,dq) + + if foundComment: tip = 'showTip dqcomm_%s_%i' % (k, run.runNr) + if extraDQInfo: + extraDQInfo = '<br><img vspace=2 height="1" width="1" src="wdot.jpg"><br><table style="width: 100%; border: 1px solid; border-width: 1px; border-spacing: 0px; border-color: #eeeeee; border-collapse: separate; padding: 0px; font-size: 65%"><tr>' + extraDQInfo + '</table>' + + if tip: s += '<td class="%s td tooltip td%s">%s%s</td>' % (tip,dqmax,dqmax,extraDQInfo) + else: s += '<td class="td td%s">%s%s</td>' % (dqmax,dqmax,extraDQInfo) + + continue + + # ----------------------------------------------------------------------------------------------------------------------- + + if "detector systems" in k.lower(): + if v!='n.a.': + v = DecodeDetectorMask(int(v),True) + s += ' <td style="min-width:%ipx"><font size="-2">%s<hr color="#aaaaaa" size=1>[<a href="http://sroe.home.cern.ch/sroe/cgi-bin/avgmodule.py?run=%i" target=_blank>SCT HV setting</a>]</font></td>' % (1.1*len(v),v,run.runNr) + continue + + # ----------------------------------------------------------------------------------------------------------------------- + + if 'olc:lumi' in k: + + # find range with stable beams (requires LHC information to be available) + hasStableBeamsInfo, xvecStb = run.getStableBeamsVector() + + # is pileup information available? + printMu = 'olc:bcidmask' in Run.SortedShowOrder() and run.runNr>=151260 and run.givepeakmuinfo + + # insert events per LB + xvec = [] + yvec = [] + yvecInt = [] + for entry in run.data[k]: + assert entry.startlb != 0, 'entry should not start at LB=0' + val = entry.value if (entry.value!='n.a.' and entry.value>0) else 0 + lastlb = min(entry.lastlb,run.nr_lb) + xvec += xrange(entry.startlb,lastlb+1) + nlb = lastlb - entry.startlb + 1 + yvec += nlb*[val] + yvecInt += nlb*[val] + + # correct for time span + intlumi, intlumiStb = (0, 0) + + # for lifetime + lifetime = 0 + lb1 = 0 + lb2 = 0 + t1 = 0 + t2 = 0 + y1 = 0 + y2 = 0 + if len(xvecStb) > 15: + lb1 = xvecStb[3] + lb2 = xvecStb[-1-5] + + peaklumi = -1 + for idx,(lbtime,lbendtime) in enumerate(run.lbtimes): + lb=idx+1 + dt = (float(lbendtime)-float(lbtime))/1.E9 + yvecInt[idx] *= dt/1000.0*run.lumiunit # unit of yvec was: ub-1 s-1 -> nb-1 + yvec[idx] *= 1.0*run.lumiunit # unit of yvec was: ub-1 s-1, transform to: 10^30 cm-2s-1 + intlumi += yvecInt[idx] + + if yvec[idx] > peaklumi: peaklumi = yvec[idx] + if lb in xvecStb: + intlumiStb += yvecInt[idx] + if lb == lb1: + t1 = lbtime/1.E9 + y1 = yvec[idx] + elif lb == lb2: + t2 = lbtime/1.E9 + y2 = yvec[idx] + + if y1 > 0 and y2 > 0: + lifetime = (t2 - t1)/(3600*math.log(y1/y2)) # unit: hours + + # line to be printed in column + if hasStableBeamsInfo: + fullprint_ = 'All LBs: %0.4g<br>Stable B: %0.4g' % (intlumi,intlumiStb) + histoText = 'Integrated luminosity: %.4g %s-1 (all LBs) and %.4g %s-1 (stable beams)' % (intlumi,run.lumiunittxt,intlumiStb,run.lumiunittxt) + else: + fullprint_ = 'All LBs: %0.4g' % (intlumi) + histoText = 'Integrated luminosity: %.4g %s-1 (all LBs)' % (intlumi,run.lumiunittxt) + + # find out which channel was used + tp1, tp2, chan = k.split(':') + try: + chan = int(chan) + if OLCAlgorithms.has_key(chan): chanstr = OLCAlgorithms[chan] + else: chanstr = 'UNKNOWN' + except ValueError: + chanstr = chan + + path = makeLBPlot( xvec, xvecStb, yvec, 'Luminosity block number', 'Online luminosity (10^{%s}cm^{-2}s^{-1})' % run.instlumiunittxt, '', + 'olclumi_vs_lb_run_%i' % (run.runNr), + 'Online lumi [%s] per LB for run %i' % (chanstr, run.runNr), + Run.Datapath, histoText ) + + # create window and tooltip + wincontent = '<table class="outer" style="padding: 5px"><tr><td>' + wincontent += '<strong><b>Online luminosity [algorithm: %s] per LB for run %i:</b></strong><br><font size="-1" color="#888888">(Begin/end of run: %s)</font>' % (chanstr, run.runNr, run.timestr('html', run.runNr != Run.runnropen)) + wincontent += '<hr color="black" size=1>' + wincontent += '<table style="padding: 0px"><tr><td>' + wincontent += '<img src="%s" align="left">' % path + wincontent += '</td></tr></table>' + wincontent += '<hr color="black" size=1>' + wincontent += '<table class="LB">' + wincontent += '<tr><th>LB</th><th>Start time<br> (%s)</th><th>Duration<br>(sec)</th><th>Inst. luminosity<br>(1e%s cm<sup>−2</sup>s<sup>−1</sup>)</th>' % (QC.tzdesc(), run.instlumiunittxt) + wincontent += '<th>Int. luminosity<br>(%s<sup>−1</sup>)</th><th>Cumul. luminosity<br>(%s<sup>−1</sup>)</th>' % (run.lumiunittxt, run.lumiunittxt) + if printMu: + wincontent += '<th> <μ> </th>' + if hasStableBeamsInfo: + wincontent += '<th>Stable beams</th>' + wincontent += '</tr><tr style="background:%s">' % '#eeeeee' + intlumi = 0 + mumax = -1 + for idx,(lbtime,lbendtime) in enumerate(run.lbtimes): + lb=idx+1 + intlumi += yvecInt[idx] + stbBeams = '−' + if lb in xvecStb: stbBeams = 'T' + timetuple = time.localtime(lbtime/1.E9) if QC.localtime else time.gmtime(lbtime/1.E9) + wincontent += '<td>%s</td><td>%s</td><td>%.2f</td><td>%.4g</td><td>%.4g</td><td>%.4g</td>' % (lb, time.strftime("%X",timetuple), (float(lbendtime)-float(lbtime))/1.E9, yvec[idx], yvecInt[idx], intlumi) + # pileup <mu> information? + if printMu: + nb = 0 + for n,v,lbstart,lbend in run.stats['olc:bcidmask']['blocks']: + if lb >= lbstart and lb <= lbend: + if v: nb = len(v[2]) + break + # compute expected number of interactions + lumi_mb = yvec[idx]*1000.0 + mu = 0 + if nb: mu = lumi_mb*71.5/11245.511/nb + wincontent += '<td>%.3g</td>' % mu + if mu > mumax: mumax = mu + if hasStableBeamsInfo: + wincontent += '<td>%s</td>' % stbBeams + if idx%2!=0: col = '#eeeeee' + else: col = '#ffffff' + wincontent += '</tr><tr style="background:%s">' % col + + printMuInfoToFile = True + if printMuInfoToFile: + mutextfilename = Run.Datapath + '/mu_vs_run_output.txt' + muout = open( mutextfilename, 'a' ) + muout.write( '%10i %f\n' % (run.runNr, mumax) ) + muout.close() + + if mumax < 0: mumax = 'n.a.' + else: mumax = "%0.3g" % mumax + + if run.runNr == Run.runnropen: # run still ongoing + wincontent += '<tr><td style="text-align:left"colspan="3"><i> Run still ongoing ...</i></td></tr>' + wincontent += '</tr></table>' + + wincontent += """<hr color="red" size=1><font color="#777777"><font size="-1"><i><font size="-2">Created by AtlRunQuery on: %s</font></i></font></td></tr></table><script type="text/javascript"></script></body></html>""" % str(datetime.datetime.now()) + wincontent += '</td></tr></table>' + + openwincmd = '<a href="javascript:openLargeWindow(' + openwincmd += "'Print','<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html xmlns:"my"><head><title>Run query result for online luminosity</title><LINK href="html/atlas-runquery-lb.css" rel="stylesheet" type="text/css"></head><body><table style="font-family: sans-serif; font-size: 85%%">" + openwincmd += wincontent + openwincmd += "')\">" + + # line to be printed in column + fullprint = '<table class="bcidtable">' + fullprint += '<tr><td colspan="2"><img src="%s" align="left" width="90px"></td></tr>' % path + if hasStableBeamsInfo: + fullprint += '<tr><td style="text-align:left">Entire run:</td><td style="text-align:left"> %0.4g %s<sup>−1</sup></td></tr><tr><td style="text-align:left">Stable beams:</td><td style="text-align:left"> %0.4g %s<sup>−1</sup></td></tr><tr><td style="text-align:left">Peak lumi:</td><td style="text-align:left"> %.2g e%s cm<sup>−2</sup>s<sup>−1</sup></td></tr><tr><td style="text-align:left">Peak <μ>:</td><td style="text-align:left"> %s</td></tr>' % (intlumi, run.lumiunittxt, intlumiStb, run.lumiunittxt, peaklumi, run.instlumiunittxt, mumax) + if lifetime > 0: + fullprint += '<tr><td style="text-align:left">Approx. lifetime:</td><td style="text-align:left"> %0.2g h</td></tr>' % (lifetime) + else: + fullprint += '<td style="text-align:left">All LBs:</td><td style="text-align:left"> %0.4g</td></tr>' % (intlumi) + fullprint += '</table>' + + # put into html page + s += '<td class="showTip OLCLumi stream" style="text-decoration:none; text-align: right">%s%s</a></td>' % (openwincmd, fullprint) + + continue + + # ----------------------------------------------------------------------------------------------------------------------- + + if 'olc:bcidmask' in k: + + firstGoodRun = 151260 + if run.runNr < firstGoodRun: # OLC information not properly filled + s += '<td style="text-align:center;color:#AA0000;font-size:60%%;">BCID information only available for run numbers larger than %i</font></td>' % firstGoodRun + continue + + # line to be printed in column + fullprint = '<font size=-2><i>Run without stable beams (or, in few cases, missing bunch information in COOL). Click here to obtain bunch crossing information</i>.</font>' + + wincontent = '<table class="outer" style="padding: 5px"><tr><td>' + wincontent += '<strong><b>Identifiers (BCID) for paired and unpaired bunches for run %i:</b></strong><br><font size="-1" color="#888888">(Begin/end of run: %s)</font>' % (run.runNr, run.timestr('html', run.runNr != Run.runnropen)) + wincontent += '<hr color="black" size=1>' + wincontent += '<table class="bcidtable" align="center"><tr class="tr0"><td style="text-align:left;font-weight:bold" colspan="3">LB range</td><td style="text-align:right;font-weight:bold"> Stable<br>beams</td><td style="text-align:right;font-weight:bold">%s</td><td style="text-align:right;font-weight:bold">%s</td><td style="text-align:right;font-weight:bold">%s</td></tr>' % (' Crossing (Paired)', '<nobr> Unpaired Beam-1 </nobr>', '<nobr> Unpaired Beam-2</nobr>') + ic = 0 + + # retrieve stable beam information + hasStableBeamsInfo, xvecStb = run.getStableBeamsVector() + + first = True + for n,v,lbstart,lbend in run.stats[k]['blocks']: + ic += 1 + cls = 'tr1' if (ic%2==1) else 'tr2' + + # is this block during stable beams ? + isInStableBeamPeriod = False + stableBeamWord = '−' + if hasStableBeamsInfo: + if lbstart <= xvecStb[-1] and lbend >= xvecStb[0]: + isInStableBeamPeriod = True + stableBeamWord = '<b>T</b>' + + wincontent += '<tr class="%s"><td class="td1" style="text-align:right"><b>%s</b></td><td style="text-align:right"><b>−</b></td><td style="text-align:right"><b>%s:</b> </td><td>%s </td>' % (cls,str(lbstart),str(lbend-1),stableBeamWord) + if v: + beam1, beam2, beam12 = v + unpaired1 = [b for b in beam1 if b not in beam12] + unpaired2 = [b for b in beam2 if b not in beam12] + + noBunches = not unpaired1 and not unpaired2 and not beam12 # happens at least in run 152508 + if not unpaired1: unpaired1 = ['−'] + if not unpaired2: unpaired2 = ['−'] + if not beam12: beam12 = ['−'] + + # analyse bunch train structure + if isInStableBeamPeriod and first and not noBunches: + first = False # don't do that again + + maxBunchDistanceInTrain = 6 + if int(run.runNr) > 168665: maxBunchDistanceInTrain = 25 # large (20) bunch distance for Heavy Ion collisions + trains = [] + bp = -1 + for b in beam12: + if bp > 0 and b - bp <= maxBunchDistanceInTrain: + if trains[-1][-1] != bp: trains.append([bp]) + trains[-1].append(b) + elif len(beam12) > 1 and bp <= 0 and beam12[0] - b <= maxBunchDistanceInTrain: + trains.append([b]) + bp = b + + fullprint = '<table class = "triggerlinktable">' + fullprint += '<tr><td style="text-align:left"><nobr>No. of coll. bunches: </nobr></td><td> <b>%i</b></td></tr>' % len(beam12) + fullprint += '<tr><td style="text-align:left"><nobr>No. of bunch trains:</nobr></td><td><b> %i</b></td></tr>' % len(trains) + if trains: + Run.TmpBoxContent = '<strong><b><nobr>Bunch train configuration for run <font color="#AA0000">%i</font></nobr></b></strong>' % run.runNr + Run.TmpBoxContent += '<hr size=1>' + + # it may be that the BCID=1 is a single pilot bunch only without a train + # in that case, don't use it to compute bunch distance in train, but print comment + icoor = 0 + if len(trains) > 1: icoor = 1 + if len(trains[icoor]) > 1: + bunchDist = trains[icoor][1] - trains[icoor][0] + fullprint += '<tr><td style="text-align:left"><nobr>Bunch dist. in trains:</nobr></td><td><nobr><b> %i</b> (%i ns)</nobr></td></tr>' % (bunchDist, bunchDist*25) + # print the comment in case of single pilot bunch + if len(trains) > 0: + if len(trains[0]) == 1: + fullprint += '<tr><td style="text-align:left;color:#4477EE" colspan=2><i>The first "bunch train" is trivial and consists of only the colliding pilot bunch</td></tr>' + fullprint += '</table><hr size=1>' + Run.TmpBoxContent += '<table><tr><td>Train</td><td><nobr>No. of bunches</nobr></td><td style="text-align:right"> BCID range</td></tr>' + for it, train in enumerate(trains): + Run.TmpBoxContent += '<tr><td> %i: </td><td> %i</td><td style="text-align:right"><nobr> %i − %i</nobr></td></tr>' % (it+1, len(train), train[0], train[-1]) + Run.TmpBoxContent += '</table><hr size=1>' + else: + Run.TmpBoxContent = '<nobr>No bunch trains in this run</nobr> <hr size=1>' + fullprint += '</table><hr size=1>' + Run.TmpBoxContent += '<p><font color="#AA0000"><b><nobr>Click to obtain full list of BCIDs</nobr></b></font>' + fullprint += '<span style="font-size: xx-small; color: #555555">[ <i>Mouse over for train configuration. Click for full list of BCIDs</i> ]</span>' + + wincontent += '<td class="td1" style="text-align:right">%s</td><td style="text-align:right; border-left:1px #C0C0D0 solid;border-right:1px #C0C0D0 solid">%s</td><td style="text-align:right">%s</td>' % (', '.join(map(str,beam12)), ', '.join(map(str,unpaired1)), ', '.join(map(str,unpaired2)) ) + else: + wincontent += '<td class="td1" style="text-align:right">%s</td><td style="text-align:right">%s</td><td style="text-align:right">%s</td>' % ('−','−','−') + + wincontent += '</tr>' + + + wincontent += '</table></td>' + wincontent += '</td></tr></table>' + + + openwincmd = HU.OpenWindow(wincontent, title="Run query result for online luminosity", extracss=None, size="large") + #openwincmd = '<a href="javascript:openLargeWindow(' + #openwincmd += "'Print','<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html xmlns:"my"><head><title>Run query result for online luminosity</title><LINK href="html/atlas-runquery-results.css" rel="stylesheet" type="text/css"></head><body><table style="font-family: sans-serif; font-size: 85%%">" + #openwincmd += wincontent + #openwincmd += "')\">" + + s += '<td class="showTip OLCBCID_%i stream" style="text-decoration:none; text-align: left">%s%s</a></td>' % (run.runNr, openwincmd, fullprint) + + continue + + # ----------------------------------------------------------------------------------------------------------------------- + + if k.startswith('bs:Pos') or k.startswith('bs:Sig') or k.startswith('bs:Tilt'): + + # reference for average + refkey = 'bs:Sig-X' # must exist ! + statkey = 'bs:Status' # must exist ! + + onloffltype = 'Online' if (run.stats['Beamspot']=='online') else 'Offline' + + # given is beamspot position or width or tilt + xvec = [] + yvec = [] + yvecSt = [] + bsidx = [] + isOK = False + vave = 0 + eave = 0 + iave = float(0) + idx = 0 + + for entry in run.data[k]: + if entry.startlb==0 and entry.lastlb==0: continue + + val,valerr = entry.value + ref = run.data[refkey].atLB(entry.startlb)[0].value[0] + stat = run.data[statkey].atLB(entry.startlb)[0].value + + lastlb = min(entry.lastlb,run.lastlb) + lbs = range(entry.startlb,lastlb+1) + nlb = len(lbs) + + xvec += [nlb] + yvec += [(val,valerr)]# * nlb + yvecSt += [stat]# * nlb + bsidx += [idx] * nlb + idx += 1 + + # compute average if BS has been determined + if ref < 10: + vave += nlb * val + eave += nlb * val**2 + iave += nlb + isOK = True + + if not isOK: + s += '<td>n.a.</td>' + continue + + if iave>0: + vave /= float(iave) + rad = eave/iave - vave*vave + if rad>=0: eave = math.sqrt(rad) + else: eave = 0 + else: + vave = -1 + + ymin = vave - 3*eave + ymax = vave + 3*eave + + if ymin >= ymax: + ymin = ymin*0.9 + ymax = ymin*1.1 + if ymin >= ymax: + ymin = -0.1 + ymax = 0.1 + + if ymin==ymax: delta = 0.1*abs(ymax) + else: delta = 0.1*(ymax-ymin) + ymin -= delta + ymax += delta + + + bstype = 'pos' + if 'Sig' in k: bstype = 'width' + elif 'Tilt' in k: bstype = 'tilt' + bstype += k[k.find('-'):] + + histoText = 'Average beam spot %s: (%.4g +- %.1g) mm' % (bstype, vave, eave) + + path = makeBSPlots( xvec=xvec, yvec=yvec, + xtitle = 'Luminosity block number', ytitle = '%s beamspot %s (mm)' % (onloffltype, bstype), + histname = '%s_vs_lb_run_%i' % (k.replace(':','_').replace('-','_'), run.runNr), + histtitle = '%s beamspot %s per LB for run %i' % (onloffltype, bstype, run.runNr), + datapath = Run.Datapath, ymin = ymin, ymax = ymax, printText = histoText ) + + + # create window and tooltip + wincontent = '<table class="outer" style="padding: 5px"><tr><td>' + wincontent += '<strong><b>%s beamspot %s per LB for run %i:</b></strong><br><font size="-1"><font color="#888888">(Begin/end of run: %s)</font></font><hr color="black" size=1>' % (onloffltype, bstype, run.runNr, run.timestr('html', run.runNr != Run.runnropen)) + wincontent += '<table style="padding: 0px"><tr><td>' + wincontent += '<img src="%s" align="left">' % (path) + wincontent += '</td></tr></table>' + wincontent += '<hr color="black" size=1>' + + wincontent += '<table style="width: auto; border: 0px solid; border-width: margin: 0 0 0 0; border-spacing: 0px; border-collapse: separate; padding: 0px; font-size: 90%; vertical-align:top; ">' + wincontent += '<tr><th style="text-align:right">LB</th><th style="text-align:right"> Start time<br> (%s)</th><th style="text-align:right"> Duration<br>(sec)</th><th style="text-align:right"> Beamspot %s<br>(mm)</th><th style="text-align:right"> Fit status</th>' % (QC.tzdesc(),bstype) + wincontent += '</tr><tr style="background:%s">' % '#eeeeee' + for idx,(lbtime,lbendtime) in enumerate(run.lbtimes): + lb=idx+1 + timetuple = time.localtime(lbtime/1.E9) if QC.localtime else time.gmtime(lbtime/1.E9) + wincontent += '<td style="text-align:right"> %s</td>' % lb + wincontent += '<td style="text-align:right"> %s</td>' % time.strftime("%X",timetuple) + wincontent += '<td style="text-align:right">%.2f</td>' % ((float(lbendtime)-float(lbtime))/1.E9) + wincontent += '<td style="text-align:right">%.4g ± %.2g</td>' % yvec[bsidx[idx]] + wincontent += '<td style="text-align:right">%i</td>' % yvecSt[bsidx[idx]] + + if idx%2!=0: col = '#eeeeee' + else: col = '#ffffff' + wincontent += '</tr><tr style="background:%s">' % col + if run.runNr == Run.runnropen: # run still ongoing + wincontent += '<tr><td style="text-align:left"colspan="3"><i> Run still ongoing ...</i></td></tr>' + wincontent += '</tr></table>' + + wincontent += """<hr color="red" size=1><font color="#777777"><font size="-1"><i><font size="-2">Created by AtlRunQuery on: %s</font></i></font></td></tr></table><script type="text/javascript"></script></body></html>""" % str(datetime.datetime.now()) + wincontent += '</td></tr></table>' + + openwincmd = '<a href="javascript:openWindow(' + openwincmd += "'Print','<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">" + openwincmd += "<html><head><title>Run query result for the %s beamspot</title><LINK href="html/atlas-runquery-lb.css" rel="stylesheet" type="text/css"></head>" % (onloffltype.lower()) + openwincmd += "<body><table style="font-family: sans-serif; font-size: 85%">" + openwincmd += wincontent + openwincmd += "</table></body></html>" + openwincmd += "')\">" + + # plot + fullprint = '<table class="triggerlinktable">' + fullprint += '<tr><td colspan="2"><img src="%s" align="left" width="90px"></td></tr>' % path + fullprint += '<tr><td colspan="2"></td></tr>' + if isOK: + fullprint += '<tr><td style="text-align:left">Average %s:</td></tr><tr><td style="text-align:left"><nobr> %0.3g ± %.2g (RMS)</nobr></td></tr>' % (bstype, vave, eave) + fullprint += '</table>' + + # put into html page + s += '<td class="showTip OFLBS stream" style="text-decoration:none; text-align: right">%s%s</a></td>' % (openwincmd, fullprint) + continue + + if "STR:" == k[0:4]: + streamName = k[4:] + if 'n.a.' in v: + s += '<td class="tdna">n.a.</td>' + continue + + # compute approximative output rate in Hz + durInSec = (run.eor-run.sor)/1.0E9 + rate = 'n.a. Hz' + try: + if durInSec > 0: rate = '%.3f Hz' % (v[0]/durInSec) + except ValueError: pass + strevents = v[0] + strsize = v[1] + # add relative fraction (only if not calibrtion string) + fracstr = '' + if not 'calibration' in k: fracstr = '%.1f%%,' % (float(strevents)/sumstrnev*100) + + + ev = {} + xvec = [] + xvecStb = [] + yvecN = [] + yvecR = [] + nevtot = 0 + + # statistics + maxrate = -1 + averate = 0 + nc = 0 + + # insert events per LB + lbrecinfo = run.stats[k]['LBRecInfo'] + + isPhysicsStream = lbrecinfo!=None and len(lbrecinfo)>0 + if isPhysicsStream: # only for physics stream we have LB wise information + ev = dict(lbrecinfo) + for lb in ev: ev[lb]=0 + for lb,nev in lbrecinfo: ev[lb] += nev + for idx,(lbtime,lbendtime) in enumerate(run.lbtimes): + lb=idx+1 + nev = 0 + if ev.has_key(lb): nev = ev[lb] + xvec.append(lb) + yvecN.append(nev) + yvecR.append(0) + nevtot += nev + try: + if float(lbendtime)-float(lbtime) > 0: + yvecR[-1] = nev/((float(lbendtime)-float(lbtime))/1.E9) + except ValueError: + pass + + lbs = ev.keys(); lbs.sort() + + # find range with stable beams (requires LHC information to be available) + hasStableBeamsInfo, xvecStb = run.getStableBeamsVector() + + # prepare pop-up window + wincontent = '<table class="outer" style="padding: 5px">' + wincontent += '<tr><td><b>Number of events and rates per LB for stream %s run %i:</b><br><font size="-1"><font color="#888888">(Begin/end of run: %s)</font></font><hr color="black" size=1>' % (streamName, run.runNr, run.timestr('html', run.runNr != Run.runnropen)) + + # create plot + pathN = makeLBPlot( xvec, xvecStb, yvecN, 'Luminosity block number', 'Number of events / LB', '', + 'nev_vs_lb_str_%s_run_%i' % (streamName, run.runNr), + 'N events per LB in stream "%s", run %i' % (streamName, run.runNr), + Run.Datapath, 'Total number of events: %i' % nevtot ) + pathR = makeLBPlot( xvec, xvecStb, yvecR, 'Luminosity block numer', 'Event rate [Hz] / LB', '', + 'rate_vs_lb_str_%s_run_%i' % (streamName, run.runNr), + 'Event rate per LB in stream "%s", run %i' % (streamName, run.runNr), + Run.Datapath ) + wincontent += '<table style="padding: 0px">\n<tr><td>' + wincontent += '<img src="%s" width="350">' % pathN.split('/')[-1] + wincontent += '</td><td>' + wincontent += '<img src="%s" width="350">' % pathR.split('/')[-1] + wincontent += '</td></tr></table>\n' + wincontent += '<hr color="black" size=1>\n' + wincontent += '<table class="lb">\n' + wincontent += '<tr><th>LB</th><th>Start time (%s)</th><th>Duration (sec)</th><th>Nevents</th><th>Cumul. Nevents</th><th>Rate [Hz]</th>' % QC.tzdesc() + if hasStableBeamsInfo: + wincontent += '<th>Stable beams</th>' + wincontent += '</tr>\n<tr style="background:%s">' % '#eeeeee' + sumnev = 0 + for idx,(lbtime,lbendtime) in enumerate(run.lbtimes): + lb=idx+1 + nev = 0 + if ev.has_key(lb): nev = ev[lb] + sumnev += nev + stbBeams = '−' + if yvecR[idx] > 0: + averate += yvecR[idx] + if yvecR[idx] > maxrate: maxrate = yvecR[idx] + nc += 1 + if lb in xvecStb: + stbBeams = 'T' + + timetuple = time.localtime(lbtime/1.E9) if QC.localtime else time.gmtime(lbtime/1.E9) + wincontent += '<td>%s</td><td>%s</td><td>%.2f</td><td>%i</td><td>%i</td><td>%.3g</td>' % (lb, time.strftime("%X",timetuple), (float(lbendtime)-float(lbtime))/1.E9, int(yvecN[idx]), sumnev, yvecR[idx]) + if hasStableBeamsInfo: + wincontent += '<td>%s</td>' % stbBeams + if idx%2!=0: col = '#eeeeee' + else: col = '#ffffff' + wincontent += '</tr>\n<tr style="background:%s">' % col + + # compute max and averages + if nc>0: averate /= float(nc) + + if run.runNr == Run.runnropen: # run still ongoing + wincontent += '<tr><td style="text-align:left"colspan="3"><i> Run still ongoing ...</i></td></tr>\n' + wincontent += '</tr></table>\n' + wincontent += '<hr color="red" size=1><font color="#777777"><font size="-1"><i><font size="-2">Created by AtlRunQuery on: %s</font></i></font></td></tr></table><script type="text/javascript"></script></body></html>' % str(datetime.datetime.now()) + wincontent += '</td></tr></table>' + else: + wincontent = "" + + # the output + tooltipkey = "STROV_%i_%s" % (run.runNr, streamName) + + physStrOnlyInfo = "" + if isPhysicsStream: + maxrate = 'max: %.2f Hz' % (maxrate) + averate = 'ave: %.2f Hz' % (averate) + physStrOnlyInfo = "%s,<br>%s,<br>%s, " % (maxrate, averate, fracstr) + debugStrLink = "" + if 'debug_' in k: + debugStrLink = ' <a href="https://trigmon.web.cern.ch/trigmon/ResultsAnalysis/RESULTS/%i/00%i_%s" target=_blank><font color="#993333">[MON]</font></a>' % (time.gmtime(run.sor/1.E9)[0], run.runNr, k[4:].strip()) + evtsize = ',<br>%s/evt' % filesize(float(strsize)/float(strevents)) if (strevents>0) else "" + tdcontent = '%s <BR><font size="-2">(%s%s/run%s%s)' % (addKommaToNumber(strevents), physStrOnlyInfo, filesize(strsize), evtsize, debugStrLink) + + # wrap content in div with tooltip + tdcontent = '<div class="showTip %s stream" style="display:inline;cursor:pointer">%s</div>' % (tooltipkey, tdcontent) + + if isPhysicsStream: + # create popup page and wrap content into href + filename = HU.CreatePopupHtmlPage( 'streamov_%i_%s' % (run.runNr, streamName), HU.WrapIntoHTML(wincontent, title="%s Stream Content" % streamName) ) + tdcontent = '<a class="externalWindow" style="text-decoration:none" href="%s">%s</a>' % (filename, tdcontent) + + s += '<td style="text-decoration:none">%s</td>' % (tdcontent) + + continue + + + + if k=="TriggerMenu": + chains = run.splitTriggerChains(v) + if len(chains)==0: s += '<td></td>' + elif len(chains)==1: s += '<td style="min-width:300px"><font size="-2">%s</font></td>' % ( chains ) + elif len(chains)==2: s += '<td style="min-width:300px"><font size="-2">%s<hr color="gray" size=1>%s</font></td>' % ( chains ) + else: s += '<td style="min-width:300px"><font size="-2">%s<hr color="gray" size=1>%s<hr color="gray" size=1>%s</font></td>' % ( chains ) + continue + + if k=="TriggerRates": + if type(v)==str: + s += '<td>%s</td>'%v + else: + tablecontent = "" + triggergroups,imgpaths,openwincmds = HU.createRatePopupWindow(v,run) + for idx,(trgroup,imgpath,openwincmd) in enumerate(zip(triggergroups,imgpaths,openwincmds)): + tooltipkey = "TrRate_Set%i_%i" % (idx,run.runNr) + contentpage = "%s/popupContent_trRates_%i_%i.html" % (Run.Datapath, run.runNr, idx) + + if idx!=0: + tablecontent += ' <tr><td colspan="2"><hr color="gray"></td></tr>' + imgcontent = '<img src="%s" align="center" width="90px"><br>' % imgpath + imagerow = '<div class="showTip %s ratespopup" id="%s" style="display:inline;cursor:pointer;font-size:80%%;">%s</div>' % (tooltipkey, contentpage, imgcontent) + tablecontent += ' <tr><td colspan="2" >%s</td></tr>' % imagerow + for tr,avr in trgroup: + tablecontent += ' <tr><td>%s</td><td>: %3.1f Hz</td></tr>' % (tr,avr) + s+="<td><table class='ratestable'>\n%s\n </table></td>" % tablecontent + continue + + if "Datasets" == k: + # separate datasets into stream types + streamtypes = ['physics_','express_','calibration_','debug_', 'debugrec_'] + steptypes = ['.daq.','.recon.','.merge.','other','Reproc' ] + datatypes = ['.RAW','.DRAW','.ESD.','.DESD','.AOD.','.TAG.','.TAG_COMM.','.HIST.','.NTUP','other'] + vetotypes = ['_TMP'] + shownlist = [] + + s += '<td>' + s += '<table class="datasettable">' + hrline = '' + allcafdsnames = '' + first = True + for stype in streamtypes: + for step in steptypes: + for dtype in datatypes: + selectedds = {} + for ds in v: + if (stype in ds[0] and step in ds[0] and dtype in ds[0]) or (stype in ds[0] and stype == 'debug_' and step == 'other' and (dtype in ds[0] or dtype == 'other')) and not ds[0] in shownlist: + vetoed = False + for veto in vetotypes: + if veto in ds[0]: + vetoed = True + break + if not vetoed: + # number of files, size, events + st = [' %s files' % (prettyNumber(ds[4]))] + st += [' %s' % (filesize(ds[5]))] + if ds[6] == 0: st += [' unkn. evts '] + else: st += [' %s evts ' % (prettyNumber(ds[6]))] + selectedds[ds[0]] = st + # replication status + if 'replicate:done' in ds[2]: selectedds[ds[0]] += [True] + else: selectedds[ds[0]] += [False] + if ds[3] == 'DONE': selectedds[ds[0]] += [True] + else: selectedds[ds[0]] += [False] + selectedds[ds[0]] += [ds[7]] # add dataset creation time + shownlist.append(ds[0]) + + if not selectedds: continue + dsnames = selectedds.keys() + dsnames.sort() + if not first: s += hrline + first = False + s += '<tr><td colspan="1"><font color="#666666"><i>[ Stream type + processing step + data type: <b>%s</b> + <b>%s</b> + <b>%s</b> ]</i> </font></td></tr>' % (stype, step, dtype) + for dsname in dsnames: + # build AMI link to dataset + dsnspl = dsname.split('.') + lk = "https://ami.in2p3.fr/AMI/servlet/net.hep.atlas.Database.Bookkeeping.AMI.Servlet.Command?Converter=/AMIXmlToAMIProdHtml.xsl&Command=FormBrowseDatasetPerParameter+-runNumber%3D" + project = dsnspl[0] + runNum = dsnspl[1] + stream = dsnspl[2] + format = dsnspl[4] + s += '<tr><td>' + s += '<a href="https://ami.in2p3.fr/AMI/servlet/net.hep.atlas.Database.Bookkeeping.AMI.Servlet.Command?Converter=/AMIXmlToAMIProdHtml.xsl&Command=FormBrowseDatasetPerParameter+-runNumber%3D' + s += runNum + "+-dataType=" + format + "+-projectName=" + project + "+-streamName=" + stream + s += '" target="_blank" title="AMI dataset information">' + s += dsname + '</td>' + res = selectedds[dsname] + s += '<td style="text-align:right"><font color="#005500"> [</font></td>' + for st in res[0:3]: + s += '<td style="text-align:right"><font color="#005500">%s</font></td>' % st + s += '<td style="text-align:right"><font color="#005500">]</font></td>' + if res[3]: + if Run.showCAFLinks: + s += '<td><a href="javascript:openPageWindow(' + s += "'data/%s.html','%s')" % (dsname,dsname) + s += '"title="Check which files of this dataset are available on ATLCAL disk pool">' + s += '<font color="#BB0517"> [ on CAF ]</font></a></td>' + + # get age of file in hours + deltat = (datetime.datetime.utcnow() - res[5]) + deltat = deltat.days*24.*3600. + deltat.seconds + allcafdsnames += dsname + '@' + '%g' % deltat + ',' + else: + s +='<td><font color="#BB0517"> [ on CAF ]</font></td>' + else: + s += '<td></td>' + if res[4]: + lk = '<a href="https://ami.in2p3.fr/AMI/servlet/net.hep.atlas.Database.Bookkeeping.AMI.Servlet.Command?Command=FormGetDQ2InfoPerDataset+-logicalDatasetName%3D%22' + dsname + '%22&Converter=%2FAMIXmlToProdFrameHtml.xsl" target="_blank" title="DQ2 information (via AMI) for dataset: ' + dsname + '">' + s += '<td>%s<font color="#153EBB"> [ in DDM ]</font></a></td>' % lk + else: + s += '<td></td>' + + s += '</tr>' + if dtype != datatypes[-1]: + hrline = '<tr><td colspan="8"><hr style="width:100%; background-color: #BBBBBB; height:1px; margin-left:0; border:0"/></td>' + else: hrline = '' + + if step != steptypes[-1]: + hrline = '<tr><td colspan="8"><hr style="width:100%; background-color: #666666; height:2px; margin-left:0; border:0"/></td>' + if stype != streamtypes[-1]: + hrline = '<tr><td colspan="8"><hr style="width:100%; background-color: #6D7B8D; height:3px; margin-left:0; border:0"/></td>' + # sanity check + s_ = '' + for ds in v: + if not 'TMP.' in ds[0] and not ds[0] in shownlist: + s_ += '<tr><td colspan=8>' + ds[0] + '</td></tr>' + if s_: + s += '<tr><td colspan="8"><hr style="width:100%; background-color: #EE0000; height:3px; margin-left:0; border:0"/></td>' + s += '<tr><td colspan=8><font color="#EE0000"><i>Remaining, unassigned datasets:</i></font></td></tr>' + s += s_ + + # close table + s += '</table>\n' + s += '</td>' + + # call subprocesses + if allcafdsnames: + subprocess.Popen('python subproc.py caf %s' % (allcafdsnames[:-1]), shell=True) + + continue + + if 'HLT PSK' == k: + # add another column with links to trigger menu for all SMK and PSKs combinations + links = '' + linklist = run.stats['PSK']['blocks'] + + if linklist: + if len(linklist)<=15: ncol = 1 + elif len(linklist)<=30: ncol = 2 + else: ncol = 3 + if len(linklist)<=15: nrow = len(linklist) + elif len(linklist)<=45: nrow = 15 + else: nrow = (len(linklist)+2)/3 + + s += '<td align="center"><table class="triggerlinktable" align="center">' + s += '<tr>' + for x in range(ncol): + s += '<td style="text-align:center;font-weight:bold" colspan="3">LB range</td><td style="text-align:center;font-weight:bold">L1 HLT</td>' + s += '</tr>' + + for row in range(nrow): + s += '<tr>' + for col in range(ncol): + entry = row + col*nrow + if entry>=len(linklist): continue + link = linklist[entry] + s += '<td style="padding-left: 0.8em">%s</td><td>−</td><td>%s: </td>' % (str(link[0]),str(link[1])) + l = '<a href="https://atlas-trigconf.cern.ch/run/smkey/%s/l1key/%s/hltkey/%s/" target="_blank" title="L1 \'%s\', HLT \'%s\'. Obtain full trigger menu corresponding to SMK=%s and prescale keys L1=%s, HLT=%s, in independent window">' % (run.result['SMK'],link[2],link[3],link[4],link[5],run.result['SMK'],link[2],link[3]) + s += '<td style="border-right: 1px solid gray; padding-right: 0.8em">%s%s | %s</a></td>' % (l, link[2],link[3]) + s += '</tr>' + s += '</table>' + s += '<hr style="width:100%; background-color: #AAAABB; height:1px; margin-left:0; border:0"/>' + s += '<a href="https://atlas-trigconf.cern.ch/psevo/%i/" target="_blank" title="Obtain prescale evolution for all L1/L2/EF chains of this run in independent window"><font size="-2"><i>Prescale evolution...</i></font></a>' % run.runNr + s+='</td>' + else: + s += '<td style="text-align:center">n.a.</td>' + continue + + if 'BGS Key' == k: + if v!='n.a.': + if len(run.stats['BGS Key' ]['blocks'])>0: + s += '<td align="center"><table class="triggerlinktable" align="center"><tr><td style="text-align:left;font-weight:bold" colspan="3">LB range</td><td style="text-align:right;font-weight:bold">BGS</td></tr>' + for bgskey, lbbeg, lbend in run.stats['BGS Key' ]['blocks']: + s += '<tr><td style="text-align:right">%i</td><td style="text-align:right">−</td><td style="text-align:right">%i: </td>' % (lbbeg, lbend-1) + l = ' <a href="https://atlas-trigconf.cern.ch/bunchgroups?key=%s" target="_blank" title="Obtain bunch group description for BGS Key=%s in independent window">' % (bgskey,bgskey) + s += '<td>%s%s</a></td></tr>' % (l, bgskey) + s += '</table>' + s+='</td>' + else: + s += '<td style="text-align:center">n.a.</td>' + else: + s += '<td class="tdna">n.a.</td>' + continue + + if "SMK" == k: + if v=='n.a.': s += '<td class="tdna">n.a.</td>' + else: + namecomment = '<div style="font-size:80%%;color:#488AC7"><b>%s</b> (v.%i)</div><div style="width:200px;font-size:60%%;color:#777777;text-align:left">%s</div>' % run.stats['SMK']['info'] + s += '<td style="text-align: center;">%s<br>%s</td>' % (v,namecomment) + continue + + if 'lar:runtype' in k or 'lar:format' in k: + info = LArConfig(k.replace('lar:','')) + try: + s += '<td style="text-align: center">%s<br><font size="-2">(%s)</font></td>' % (v,info[int(v)]) + except ValueError: + if v=='n.a.': s += '<td class="tdna">n.a.</td>' + else: s += '<td style="text-align: right">%s</td>' % v + continue + + if 'BPM' == k: + + # compute average and RMS BPMs + fullprint = '<table class="triggerlinktable" align="center">' + keys = v.keys() + keys.sort() + + # time results + xvec = [] + yvec = [] + toffset = int(run.sor/1.E9) + deltat = int(run.eor/1.E9) - int(run.sor/1.E9) + + # LB axis + xveclb = [] + for idx,(lbtime,lbendtime) in enumerate(run.lbtimes): + lb=idx+1 + xveclb.append( [lbtime/1.E9-toffset, (float(lbendtime)-float(lbtime))/1.E9] ) + + ymin = +1e30 + ymax = -1e30 + legend = [] + datemin = '' # run sor + datemax = '' # run eor + for ik,key in enumerate(keys): + y = 0 + y2 = 0 + xvec.append([]) + yvec.append([]) + for (val,dte) in v[key]: + y += val + y2 += val*val + + ts = time.strptime( dte[:dte.rfind(':')] + '/UTC','%d-%m-%Y %H:%M:%S/%Z' ) + tsec = int(calendar.timegm(ts)) + xvec[-1].append(tsec - toffset) # time defined with respect to offset + yvec[-1].append(val) + + ic = len(v[key]) + if ic > 0: + y /= float(ic) + arg = y2/float(ic) - y*y + if arg > 0: y2 = math.sqrt(arg) + else: y2 = 0 + ymin = min(ymin,y - 1.5*y2) + ymax = max(ymax,y + 1.5*y2) + + # fill table + vname = 'unknown' + if 'B1' in key and 'hori' in key: + vname = 'B1 horiz. IP' + lname = 'B1 horizont IP pos.' + elif 'B1' in key and 'vert' in key: + vname = 'B1 vert. IP' + lname = 'B1 vertical IP pos.' + elif 'B2' in key and 'hori' in key: + vname = 'B2 horiz. IP' + lname = 'B2 horizontal IP pos.' + elif 'B2' in key and 'vert' in key: + vname = 'B2 vert. IP' + lname = 'B2 vertical IP pos.' + legend.append( lname ) + fullprint += '<tr><td style="text-align:left">%s: </td><td>(</td><td style="text-align:right"> %s</td><td style="text-align:left"> ± </td><td style="text-align:left">%.4g</td><td>) um</td><td> </td>' % (vname, ('%.4g' % y).replace('-','−'), y2) + + if ik == 0: fullprint += '<td rowspan="4">REPLACEME</td>' + fullprint += '</tr>' + + # write to file (as text and TGraphs) + bpmrootfilename = Run.Datapath + '/bpm_output_run%i.root' % run.runNr + SaveGraphsToFile( bpmrootfilename, keys, xvec, yvec, "TimeOffset", toffset ) + + bpmtextfilename = Run.Datapath + '/bpm_output_run%i.txt' % run.runNr + bpmout = open( bpmtextfilename, 'w' ) + + bpmout.write( '# BPM Output for run %i\n' % run.runNr ) + bpmout.write( '# Start - end of run: %s [UTC]\n' % run.timestr('txt') ) + bpmout.write( '# Time offset applied below in seconds: %i\n' % int(toffset) ) + bpmout.write( '#\n' ) + bpmout.write( '# ---------------------------------------------------------------------------------\n' ) + for ik,key in enumerate(keys): + bpmout.write( '# Output for variable: "%s"\n' % key ) + bpmout.write( '# Format: index / timestamp (seconds) wrt. begin of run / BPM position in microns\n' ) + bpmout.write( '#\n' ) + for i in range(len(xvec[ik])): + bpmout.write( "%4i %9.2f %14.6f\n" % (i, xvec[ik][i], yvec[ik][i]) ) + bpmout.write( '#\n' ) + bpmout.write( '# ---------------------------------------------------------------------------------\n' ) + bpmout.close() + + fullprint += '<tr><td colspan=8><hr style="width:100%; background-color:#999999; height:1px; margin-left:0; border:0"/></td></tr>' + fullprint += '<tr><td colspan=8 style="text-align:left"><a href="%s" target="_blank" title="Tabulated BPM information versus time for run %i"><i>Click here for tabulated BPM values versus time...</i></a></td></tr>' % (bpmtextfilename, run.runNr) + fullprint += '<tr><td colspan=8 style="text-align:left"><a href="%s" target="_blank" title="BPM information versus time for run %i as four TGraph objects in ROOT file"><i>Right-click to download ROOT file with TGraphs...</i></a></td></tr>' % (bpmrootfilename, run.runNr) + fullprint += '</table>' + + # make plot + path = makeTimePlotList( xvec, xveclb, yvec, toffset, deltat, ymin, ymax, + 'Time of day (UTC)', 'Beam position (microns)', legend, + 'bp_vs_time_run_%i' % (run.runNr), + 'Beam position versus time of day for run %i' % (run.runNr), + Run.Datapath, '' ) + + replacetxt = '<img src="%s" align="left" width="70px">' % path + fullprint = fullprint.replace('REPLACEME',replacetxt) + + # create window and tooltip + wincontent = '<table class="outer" style="padding: 5px"><tr><td>' + wincontent += '<strong><b>LHC beam positions at IP verssu time for run %i:</b></strong><br><font size="-1"><font color="#888888">(Begin/end of run: %s)</font></font><hr color="black" size=1>' % (run.runNr, run.timestr('html', run.runNr != Run.runnropen)) + wincontent += '<table style="padding: 0px"><tr><td>' + wincontent += '<img src="%s" align="left"></td>' % path + wincontent += '</td></tr></table>' + wincontent += """<hr color="red" size=1><font color="#777777"><font size="-1"><i><font size="-2">Created by AtlRunQuery on: %s</font></i></font></td></tr></table><script type="text/javascript"></script></body></html>""" % str(datetime.datetime.now()) + wincontent += '</td></tr></table>' + + + openwincmd = HU.OpenWindow(wincontent,title="Summary of LHC information") + + # put into html page + s += '<td style="text-decoration:none">%s<div class="showTip BPM stream" style="display:inline;cursor:pointer">%s</div></a></td>' % (tooltipkey, fullprint) + continue + + if 'ofllumi:' in k: + + if ofllumiFlag: continue + ofllumiFlag = True + + # find range with stable beams (requires LHC information to be available) + hasStableBeamsInfo, xvecStb = run.getStableBeamsVector() + + # find all channels that should be printed + kref = [] + chans = [] + tags = [] + for kp in Run.ShowOrder: + if 'ofllumi:' in kp.ResultKey: + name, chanstr, tagstr = kp.ResultKey.split(':') + kref.append(kp) + chans.append(chanstr) + tags.append(tagstr) + + # get the channel names + chanstr = [] + for ch in chans: + ich = int(ch) + if OLCAlgorithms.has_key(ich): chanstr.append(OLCAlgorithms[ich]) + else: chanstr.append('Unknown') + + # insert events per LB + xvec = [] + yvec = [] + peaklumi = [] + intlumi = [] + intlumiStb = [] + for ikp,kp in enumerate(kref): + yvec.append([]) + peaklumi.append(-1) + intlumi.append(0) + intlumiStb.append(0) + for entry in run.data[kp]: + if entry.startlb == 0: continue + val = entry.value if entry.value!='n.a.' and entry.value>0 else 0 + lastlb = min(entry.lastlb,run.lastlb) + lbs = range(entry.startlb,lastlb+1) + if ikp == 0: xvec += lbs + yvec[-1] += len(lbs)*[float(val)] + + + for idx,(lbtime,lbendtime) in enumerate(run.lbtimes): + lb=idx+1 + dt = (float(lbendtime)-float(lbtime))/1.E9 + for ich in range(len(chans)): + tmp = yvec[ich] + intlumi[ich] += tmp[idx]*dt/1000.0 # unit of yvec was: ub-1 s-1 -> nb-1 + if lb in xvecStb: intlumiStb[ich] += yvec[ich][idx]*dt/1000.0 + + yvec[ich][idx] *= 1.0 # unit of yvec was: ub-1 s-1, transform to: 10^30 cm-2s-1 + if yvec[ich][idx] > peaklumi[ich]: peaklumi[ich] = yvec[ich][idx] + + # line to be printed in column + fullprint_ = '' + if hasStableBeamsInfo: + for ich,ch in enumerate(chans): + fullprint_ += 'Integrated (channel: %s): %0.4g<br>Stable B: %0.4g<br>' % (ch,intlumi[ich],intlumiStb[ich]) + histoText = 'Integr. lumi (channel: %i): %.4g nb-1 (all LBs) and %.4g nb-1 (stable beams)' % (0, intlumi[0],intlumiStb[0]) + else: + for ich,ch in enumerate(chans): + fullprint_ += 'Integrated (channel: %s): %0.4g<br>' % (ch,intlumi[ich]) + histoText = 'Integr. lumi (channel: %i): %.4g nb-1 (all LBs)' % (0, intlumi[0]) + + # make plot + path = makeLBPlotList( xvec, xvecStb, yvec, + 'Luminosity block number', 'Offline luminosity (10^{30}cm^{-2}s^{-1})', chanstr, + 'ofllumi%s_vs_lb_run_%i' % (chans[0],run.runNr), + 'Offline lumi per LB for run %i' % (run.runNr), + Run.Datapath, histoText ) + + # create window and tooltip + wincontent = '<table class="outer" style="padding: 5px"><tr><td>' + wincontent += '<strong><b>Offline luminosity per LB for run %i:</b></strong><br><font size="-1" color="#888888">(Begin/end of run: %s)</font>' % (run.runNr, run.timestr('html', run.runNr != Run.runnropen)) + wincontent += '<hr color="black" size=1>' + wincontent += '<table style="padding: 0px"><tr><td>' + wincontent += '<img src="%s" align="left">' % path + wincontent += '</td></tr></table>' + wincontent += '<hr color="black" size=1>' + wincontent += '<table class="LB">' + wincontent += '<tr><th>LB</th><th>Start time<br> (%s)</th><th>Duration<br>(sec)</th>' % QC.tzdesc() + for ch in chanstr: + wincontent += '<th>Inst. luminosity<br>(1e%s cm<sup>−2</sup>s<sup>−1</sup>)<br>[ %s ]</th>' % (run.instlumiunittxt,ch) + wincontent += '<th>Stable beams</th>' + wincontent += '</tr><tr style="background:%s">' % '#eeeeee' + for idx,(lbtime,lbendtime) in enumerate(run.lbtimes): + lb=idx+1 + stbBeams = '−' + if lb in xvecStb: stbBeams = 'T' + timetuple = time.localtime(lbtime/1.E9) if QC.localtime else time.gmtime(lbtime/1.E9) + wincontent += '<td>%i</td><td>%s</td><td>%.2f</td>' % (lb, time.strftime("%X",timetuple), (float(lbendtime)-float(lbtime))/1.E9) + for ich in range(len(chans)): + wincontent += '<td>%.4g</td>' % (yvec[ich][idx]) + wincontent += '<td>%s</td>' % stbBeams + if idx%2!=0: col = '#eeeeee' + else: col = '#ffffff' + wincontent += '</tr><tr style="background:%s">' % col + if run.runNr == Run.runnropen: # run still ongoing + wincontent += '<tr><td style="text-align:left"colspan="3"><i> Run still ongoing ...</i></td></tr>' + wincontent += '</tr></table>' + wincontent += """<hr color="red" size=1><font color="#777777"><font size="-1"><i><font size="-2">Created by AtlRunQuery on: %s</font></i></font></td></tr></table><script type="text/javascript"></script></body></html>""" % str(datetime.datetime.now()) + wincontent += '</td></tr></table>' + + openwincmd = HU.OpenWindow(wincontent,title="Run query result for online luminosity") + + # line to be printed in column + fullprint = '<table class="bcidtable">' + fullprint += '<tr><td colspan="2"><img src="%s" align="left" width="90px"></td></tr>' % path + if hasStableBeamsInfo: + for ich in range(len(chans)): + fullprint += '<tr><td style="text-align:left">Entire run:</td><td style="text-align:left"> %0.4g %s<sup>−1</sup></td></tr><tr><td style="text-align:left">Stable beams:</td><td style="text-align:left"> %0.4g %s<sup>−1</sup></td></tr><tr><td style="text-align:left">Peak lumi:</td><td style="text-align:left"> %0.2g e%s cm<sup>−2</sup>s<sup>−1</sup></td></tr>' % (intlumi[ich], run.lumiunittxt, intlumiStb[ich], run.lumiunittxt, peaklumi[ich], run.instlumiunittxt) + else: + for ich in range(len(chans)): + fullprint += '<td style="text-align:left">All LBs:</td><td style="text-align:left"> %0.4g</td></tr>' % (intlumi[ich]) + fullprint += '</table>' + + # put into html page + s += '<td class="showTip OFLLumi stream" style="text-decoration:none; text-align: right">%s%s</a></td>' % (openwincmd, fullprint) + continue + + if any( [ ('lhc:' in k and not 'beamenergy' in k), + ('TorCurrent' in k), + ('SolCurrent' in k), + ('bs:' in k), + ('Ready for physics' in k), + ] ): + res = [] + keys = [] + + # search unique ranges + for entry in run.data[k]: + if entry.startlb == 0: continue + val = entry.value + + try: + val = float(val) + if not 'bs:' in k and not 'ofllumi:' and not 'beam1intensity' in k and not 'beam2intensity' in k: val = round(val) # no floating points, unless it is 'bs' + except ValueError: pass + + # special treatment for E-beam = 7864 + if 'beamenergy' in k and val>=7864: val = 'ASYNC' + + if not keys or res[-1] != val: + if keys: keys[-1][1] = entry.startlb + res.append ( val ) + keys.append( [entry.startlb, entry.endlb] ) + elif res[-1] == val: + keys[-1][1] = entry.endlb + + + if len(keys) > 1: + s += '<td><table class="triggerlinktable" align="center">' + for i, c in enumerate(keys): + if int(c[0]) != int(c[1])-1: + s += '<tr><td>LB </td><td style="text-align:right">%i</td><td style="text-align:right">−</td><td style="text-align:right">%i: </td>' % (int(c[0]),int(c[1])-1) + else: + s += '<tr><td>LB </td><td style="text-align:right">%i</td><td style="text-align:right"></td><td style="text-align:right">: </td>' % (int(c[0])) + try: + val = float(res[i]) + if 'bs:' in k or 'beam1intensity' in k or 'beam2intensity' in k: + vt = '%.4g' % abs(val) + if val < 0: vt = '−' + vt + if 'e-' in vt: vt = vt.replace('e-','e−') + s += '<td style="text-align:right">%s</td></tr>' % vt + elif 'ofllumi:' in k: + s += '<td style="text-align:right">%g</td></tr>' % val + else: + s += '<td style="text-align:right">%i</td></tr>' % round(val) + except ValueError: + s += '<td style="text-align:left">%s</td></tr>' % res[i].strip() + s += '</table></td>' + + else: + try: + if 'bs:' in k: + vt = '%.4g' % abs(float(v)) + if float(v) < 0: v = '−' + vt + else: v = vt + if 'e-' in v: v = v.replace('e-','e−') + else: + v = round(float(v)) + if 'beamenergy' in k and v >= 7864: v = 'ASYNC' + else: v = '%i' % v + except ValueError: pass + s += '<td style="text-align:center;font-size: 85%%">%s</td>' % v + + + if ('lhc:stablebeams' in k and 'lhc:beamenergy' in Run.ShowOrder and not 'lhc:beammode' in Run.ShowOrder) or 'lhc:beammode' in k: + imgpath, xvec, yvec, ymax = HU.makeSummaryPlotForLHC(run) + wincontent = HU.makeSummaryPageForLHC(run, yvec, imgpath) + openwincmd = HU.OpenWindow(wincontent, 'Summary of LHC information') + fullprint = '<table class="bcidtable">' + fullprint += '<tr><td><img src="%s" align="left" width="90px"></td></tr>' % imgpath + fullprint += '<tr><td style="text-align:left">Maximum intensities:<br>Beam 1: %0.3g e11 protons<br>Beam 2: %0.3g e11 protons</td></tr>' % (ymax[0], ymax[1]) + fullprint += '<tr><td style="text-align:left">Maximum beam energy:<br>%i GeV</td></tr>' % int(ymax[2]) + fullprint += '</table>' + fullprint += '<span style="font-size: xx-small; color: #555555">[ <i>Numbers given for stable beam period (if applies)</i> ]</span>' + # put into html page + s += '<td class="showTip LHCSummary stream" style="text-decoration:none; text-align: left">%s %s </a></td>' % (openwincmd, fullprint) + + continue + + # all others + if v=='n.a.': s += '<td class="tdna">n.a.</td>' + else: s += '<td style="text-align: right">%s</td>' % v + + + s += "</tr>\n\n" + + ### + ### TOOLTIPS + ### + + # here we start filling the tooltips. there are quite a lot and we should be a bit smarter about them. + # For instance, there is no need to define the whole table for each single box + + with timer("Tooltips"): + createToolTips(run) + + return s + + + +def createToolTips(run): + import CoolRunQuery.html.AtlRunQueryHtmlUtils as HU + + HU.CreateLBTooltip(run) + + # DQ DEFECTS + if 'DQ' in Run.ShowOrder: + HU.CreateDQTooltip(run) + + # stream overlaps + for k in [k for k in Run.ShowOrder if k.Type==DataKey.STREAM and k.ResultKey in run.stats]: + HU.CreateStreamOverlapTooltip(run, k) + + # DQ (old) + for data_key in Run.ShowOrder: + k = data_key.ResultKey + if not isDQ(k): continue + if not k in run.stats: continue + commentbox = '' + for (dq,comment), start, end in run.stats[k]["blocks"]: + if dq != 'n.a.': + if not comment: comment = 'no comment' + col = "#000000" + if dq == 'R': col = "#AA0000" + elif dq == 'G': col = "#006600" + elif dq == 'Y': col = "#716309" + if start == end-1: + commentbox += '<tr style="color:%s"><td style="vertical-align:top;">LB </td><td style="text-align:right;vertical-align:top;">%s</td><td style="text-align:right;vertical-align:top;"></td><td style="text-align:right;vertical-align:top;"> (%s) : </td><td style="text-align:left;vertical-align:top;"><i><strong><b>"%s"</b></strong></i></td></tr>' % (col,start,dq,comment) + else: + commentbox += '<tr style="color:%s"><td style="vertical-align:top;">LB </td><td style="text-align:right;vertical-align:top;">%s</td><td style="text-align:right;vertical-align:top;">−</td><td style="text-align:right;vertical-align:top;">%s (%s) : </td><td style="text-align:left;vertical-align:top;"><i><strong><b>"%s"</b></strong></i></td></tr>' % (col,start,end-1,dq,comment) + else: + if start == end-1: + commentbox += '<tr style="color:#222222"><td style="vertical-align:top;">LB </td><td style="text-align:right;vertical-align:top;">%s</td><td style="text-align:right;vertical-align:top;"></td><td style="text-align:right;vertical-align:top;"> (n.a.) </td><td style="text-align:left;vertical-align:top;"></td></tr>' % (start) + else: + commentbox += '<tr style="color:#222222"><td style="vertical-align:top;">LB </td><td style="text-align:right;vertical-align:top;">%s</td><td style="text-align:right;vertical-align:top;">−</td><td style="text-align:right;vertical-align:top;">%s (n.a.) </td><td style="text-align:left;vertical-align:top;"></td></tr>' % (start,end-1) + if commentbox: + sp = k.split(':') + commentbox = '<strong><b>Comments for %s DQ channel "%s" in run %s:</b></strong>' % (sp[0],sp[1],run.runNr) + '<table>' + commentbox + commentbox += '</table>' + run.addToolTip( "dqcomm_%s_%i" % (k, run.runNr), commentbox) + + + # trigger keys + lbtrigtypes = { 'L1 PSK': ['L1 PSK', 'L1K'], 'HLT PSK' : ['HLT PSK','HLTK'] } + for lbtrigtype, c in lbtrigtypes.iteritems(): + if lbtrigtype in Run.ShowOrder and run.stats[lbtrigtype].has_key('blocks') and len(run.stats[lbtrigtype]['blocks'])>1: + boxcontent = '<strong><b>%s evolved during run:</b></strong><br>' % c[0] + boxcontent += '<table style="width: auto; border: 0px solid; border-width: margin: 0 0 0 0; 0px; border-spacing: 0px; border-collapse: separate; padding: 0px; font-size: 100%">' + for item, start, stop in run.stats[lbtrigtype]['blocks']: + boxcontent += '<tr><td>LB </td><td style="text-align:right">%s</td><td style="text-align:right">−</td><td style="text-align:right">%s</td><td style="text-align:right">: %s</td></tr>' % (start,stop-1,item) + boxcontent += '</table>' + run.addToolTip("%s_%i" % (c[1], run.runNr), boxcontent) + + + # OLC BCID + if any([k for k in Run.ShowOrder if "olc:bcidmask" in k.ResultKey]): + boxcontent = Run.TmpBoxContent + if boxcontent: + Run.addGlobalToolTip("OLCBCID_%i" % run.runNr, boxcontent) + diff --git a/Database/CoolRunQuery/python/AtlRunQuerySFO.py b/Database/CoolRunQuery/python/AtlRunQuerySFO.py new file mode 100644 index 00000000000..44c660df934 --- /dev/null +++ b/Database/CoolRunQuery/python/AtlRunQuerySFO.py @@ -0,0 +1,438 @@ +#!/bin/env python + +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration +# +# ---------------------------------------------------------------- +# Script : AtlRunQuerySFO.py +# Project: AtlRunQuery +# Purpose: Utility to retrieve information from SFO DB +# Authors: Andreas Hoecker (CERN), Joerg Stelzer (DESY) +# Created: Nov 13, 2008 +# ---------------------------------------------------------------- +# +# SFO DB schema: +# ============================================== +# +# Run Table +# +# "SFOID" "VARCHAR2(32 Bytes)" +# "RUNNR" "NUMBER(10,0)" +# "STREAMTYPE" "VARCHAR2(32 Bytes)" +# "STREAM" "VARCHAR2(32 Bytes)" +# "STATE" "VARCHAR2(32 Bytes)" [OPENED,CLOSED,TRANSFERRED] +# "TZSTATE" "VARCHAR2(32 Bytes)" filled by TZ +# "DSNAME" "VARCHAR2(256 Bytes)" +# "T_STAMP" "TIMESTAMP(0)" "RUN_SEQNUM" "NUMBER(10,0)" +# +# Lumiblock Table +# +# "SFOID" "VARCHAR2(32 Bytes)" +# "RUNNR" "NUMBER(10,0)" +# "LUMIBLOCKNR" "NUMBER(10,0)" +# "STREAMTYPE" "VARCHAR2(32 Bytes)" +# "STREAM" "VARCHAR2(32 Bytes)" +# "STATE" "VARCHAR2(32 Bytes)" [OPENED,CLOSED,TRANSFERRED] +# "TZSTATE" "VARCHAR2(32 Bytes)" filled by TZ +# "T_STAMP" "TIMESTAMP(0)" "LUMIBLOCK_SEQNUM" "NUMBER(10,0)" +# +# File Table +# +# "LFN" "VARCHAR2(256 Bytes)" filename +# "FILENR" "NUMBER(10,0)" +# "SFOID" "VARCHAR2(32 Bytes)" +# "RUNNR" "NUMBER(10,0)" +# "LUMIBLOCKNR" "NUMBER(10,0)" +# "STREAMTYPE" "VARCHAR2(32 Bytes)" +# "STREAM" "VARCHAR2(32 Bytes)" +# "SFOHOST" "VARCHAR2(64 Bytes)" +# "TRANSFERSTATE" "VARCHAR2(32 Bytes)" [ONDISK,TRANSFERRED] +# "FILESTATE" "VARCHAR2(32 Bytes)" [OPENED,CLOSED,DELETED] +# "FILEHEALTH" "VARCHAR2(32 Bytes)" [SANE,TRUNCATED] +# "TZSTATE" "VARCHAR2(32 Bytes)" filled by TZ +# "FILESIZE" "NUMBER(10,0)" +# "GUID" "VARCHAR2(64 Bytes)" +# "CHECKSUM" "VARCHAR2(64 Bytes)" Adler-32 +# "PFN" "VARCHAR2(1024 Bytes)" /path/filename on Castor +# "SFOPFN" "VARCHAR2(1024 Bytes)" /path/filename on SFO +# "NREVENTS" "NUMBER(10,0)" +# "T_STAMP" "TIMESTAMP(0)" "ENDTIME" "TIMESTAMP(0)" UTC time +# "FILE_SEQNUM" "NUMBER(10,0)" +# +# Index Table +# +# "SEQNAME" "VARCHAR2(64 Bytes)" +# "SEQINDEX" "NUMBER(10,0)" +# "LASTMOD" "TIMESTAMP(0)" +# +# Schema of new OVERLAP table: +# +# "SFOID" VARCHAR2(32 BYTE) +# "RUNNR" NUMBER(10,0) +# "REFERENCE_STREAM" VARCHAR2(128 BYTE) +# "OVERLAP_STREAM" VARCHAR2(128 BYTE) +# "OVERLAP_EVENTS" NUMBER(10,0) +# "OVERLAP_RATIO" BINARY_FLOAT +# "TYPE" VARCHAR2(32 BYTE) [RAW, FILE] +# "T_STAMP" TIMESTAMP (0) +# "OVERLAP_SEQNUM" NUMBER(10,0) +# +# +# Each row provides the overlap for a stream pair (in a given run and +# SFO). The pair is composed by a reference stream and an overlap stream. +# We can distinguish between two cases: +# +# a) reference stream = overlap stream +# The row provides the total number of events in the stream +# (OVERLAP_EVENTS) and OVERLAP_RATIO is 1 +# +# b) reference stream != overlap stream +# The row provides the overlap between the two streams (OVERLAP_EVENTS). +# OVERLAP_RATIO is defined as OVERLAP_EVENTS/#(events for reference stream). +# +# The attached screenshot is an example of the expected result (with two +# streams only). +# +# +# The TYPE column is needed to distinguish the source level of the counters: +# +# RAW: raw streams at the input of the SFO +# FILE: resulting streams after the SFO stream processing +# (inclusive/exclusive, late, ...) +# + +from __future__ import with_statement +from utils.AtlRunQueryTimer import timer + +from utils.AtlRunQueryCache import Cache +from .AtlRunQueryRun import Run + +import cx_Oracle +from time import time +from collections import defaultdict + +@Cache("cache/sfo","%s_run_%i") +def executeCachedQuery(run,cu,q,cachekey): + cu.execute(q, run=run) + return cu.fetchall() + +def executeQuery(run,cu,q,cachekey): + cu.execute(q, run=run) + return cu.fetchall() + + + +############################################################## + +# The following 5 methods are used in the stream selector + +############################################################## + +def GetSFO_streamsAll( cursor, runlist ): + res = defaultdict(list) + q = "SELECT DISTINCT STREAMTYPE, STREAM FROM SFO_TZ_RUN WHERE RUNNR=:run" + cursor.prepare(q) + + for run in runlist: + cursor.execute(q,run=run) + qr = cursor.fetchall() + for e in qr: + res[run].append("%s_%s" % (e[0],e[1])) + + return res + + +def GetSFO_filesAll( cursor, runlist ): + """returns nfiles, fsize, nevents""" + res = defaultdict(dict) + q = "SELECT STREAMTYPE, STREAM, COUNT(FILESIZE), SUM(FILESIZE), SUM(NREVENTS) FROM SFO_TZ_File WHERE RUNNR = :run GROUP BY STREAM, STREAMTYPE" + cursor.prepare(q) + + for run in runlist: + if run==Run.runnropen: + qr = executeQuery(run, cursor, q, cachekey=("filesAll",run)) + else: + qr = executeCachedQuery(run, cursor, q, cachekey=("filesAll",run)) + for e in qr: + key = "%s_%s" % (e[0],e[1]) + res[run][key] = e[2:5] # #files, filesize, nevents + return res + + + +def GetSFO_LBsAll( cursor, runlist ): + res = defaultdict(dict) + q = "SELECT STREAMTYPE, STREAM, MIN(LUMIBLOCKNR), MAX(LUMIBLOCKNR), COUNT(DISTINCT LUMIBLOCKNR) FROM SFO_TZ_Lumiblock WHERE RUNNR=:run GROUP BY STREAM, STREAMTYPE" + cursor.prepare(q) + + for run in runlist: + if run==Run.runnropen: + qr = executeQuery(run, cursor, q, cachekey=("LBsAll",run)) + else: + qr = executeCachedQuery(run, cursor, q, cachekey=("LBsAll",run)) + for e in qr: + key2 = "%s_%s" % (e[0],e[1]) + res[run][key2] = e[2:5] # minlb,maxlb,nlb + return res + + + +def GetSFO_NeventsAll( cursor, runlist ): + # due to the amount of information oracle access is faster than file access + res = {} + q = "SELECT STREAMTYPE, STREAM, LUMIBLOCKNR, SUM(NREVENTS) FROM SFO_TZ_File WHERE RUNNR=:run GROUP BY STREAMTYPE, STREAM, LUMIBLOCKNR ORDER BY LUMIBLOCKNR" + cursor.prepare(q) + + for run in runlist: + if run==Run.runnropen: + qr = executeQuery(run, cursor, q, cachekey=("NeventsAll",run)) + else: + qr = executeCachedQuery(run, cursor, q, cachekey=("NeventsAll",run)) + res[run] = defaultdict(list) + for e in qr: + key2 = "%s_%s" % (e[0],e[1]) + res[run][key2].append(e[2:4]) # lbNr, NrEv + + return res + + +def GetSFO_overlapAll( cursor, runlist ): + """returns number of overlap events""" + res = {} + q = "SELECT REFERENCE_STREAM, OVERLAP_STREAM, SUM(OVERLAP_EVENTS) FROM SFO_TZ_Overlap WHERE TYPE='FILE' and RUNNR=:run GROUP BY REFERENCE_STREAM, OVERLAP_STREAM" + cursor.prepare(q) + + for run in runlist: + if run==Run.runnropen: + qr = executeQuery(run, cursor, q, cachekey=("overlapAll",run)) + else: + qr = executeCachedQuery(run, cursor, q, cachekey=("overlapAll",run)) + + res[run] = defaultdict(dict) + for e in qr: + res[run][e[0]][e[1]] = e[2] # overlap + + return res + + + + +############################################################## + +# The following function is used by the XML file maker + +# we store only information about physics streams + +############################################################## + + +def GetSFO_NeventsAllPhysics( cursor, runlist ): + res = {} + q = "SELECT STREAM, LUMIBLOCKNR, SUM(NREVENTS) FROM SFO_TZ_File WHERE RUNNR=:run AND STREAMTYPE='physics' GROUP BY STREAM, LUMIBLOCKNR ORDER BY LUMIBLOCKNR" + cursor.prepare(q) + + for run in runlist: + if run==Run.runnropen: + qr = executeQuery(run, cursor, q, cachekey=("NeventsAllPhysics",run)) + else: + qr = executeCachedQuery(run, cursor, q, cachekey=("NeventsAllPhysics",run)) + res[run] = defaultdict(list) + for e in qr: + key2 = "physics_%s" % e[0] + res[run][key2].append(e[1:3]) # lbNr, NrEv + + return res + + + +############################################################## + +# The following function is used by the EventSelector + +############################################################## + +def GetSFO_NphysicseventsAll( cursor, runlist ): + res = {} + q = "SELECT SUM(OVERLAP_EVENTS) from SFO_TZ_OVERLAP WHERE RUNNR=:run AND TYPE='EVENTCOUNT' AND REFERENCE_STREAM='physics_EventCount'" + cursor.prepare(q) + for run in runlist: + if run==Run.runnropen or 1: # FIXME problem in cache + qr = executeQuery(run, cursor, q, cachekey=("SumOverlap",run)) + else: + qr = executeCachedQuery(run, cursor, q, cachekey=("SumOverlap",run)) + for e in qr: + if e[0]!=None: res[run] = e[0] + return res + + + + +def GetSFO_lastNruns( cursor, nruns ): + """returns list of last 'nruns' run numbers, where at least one stream is not calibration""" + cursor.execute( "SELECT RUNNR,STATE FROM (SELECT UNIQUE RUNNR,STATE FROM SFO_TZ_RUN WHERE STREAMTYPE!='calibration' AND RUNNR < 999999 ORDER BY RUNNR DESC) SFO_TZ_RUN2 WHERE rownum<=:arg_1", arg_1=nruns ) + return cursor.fetchall() + +def runInfo(runno, streams): + if streams==[]: + print "No information available for run %i" % runno + return + + print 'Output for run number: %i' % runno + print '--------------------------------------------------------------------------------' + print 'Streams:' + print streams + print ' ' + print ' ' + for s in streams: + minlb, maxlb, lbs = GetSFO_LBs( cursor, runno, s ) + print 'For Stream: %25s: min - max LBs: %i - %i \t(Num: %i)' % (s, minlb, maxlb, lbs) + + result = GetSFO_Nevents( cursor, runno, s ) + lbold = -1 + allnev = 0 + for lb,nev in result: + if lb != lbold: + if lbold != -1: print s,'\t',lbold,'\t',allnev + allnev = nev + lbold = lb + else: + allnev += nev + #for lb in range(minlb,maxlb+1): + #nev = GetSFO_NeventsPerLB( cursor, runno, s, lb ) + #print ' - LB: %i has %s events' % (lb,nev) + + print ' ' + return + totnf = totsize = totev = 0 + for s in streams: + nfiles, size, events = GetSFO_files( cursor, runno, s ) + totnf += nfiles + totsize += size + totev += events + print 'Stream %-25s : %i files, %.2f GB, %i events' % (s, nfiles, size/1.0e9, events) + + print '--------------------------------------------------------------------------------' + print 'Total %-25s : %i files, %.2f GB, %.2f mio events' % (' ', totnf, totsize/1.0e9, totev/1.0e6) + print '--------------------------------------------------------------------------------' + + print 'Check overlaps for stream pairs' + for i in range(0,len(streams)): + nfilesi, sizei, eventsi = GetSFO_files( cursor, runno, streams[i] ) + for j in range(i,len(streams)): + nfilesj, sizej, eventsj = GetSFO_files( cursor, runno, streams[i] ) + eventsij = GetSFO_overlap( cursor, runno, streams[i], streams[j] ) + if eventsij: + print ' [ %28s, %28s ] = %g' % (streams[i], streams[j], eventsij) + else: + print ' [ %28s, %28s ] = None' % (streams[i], streams[j]) + + + +############################################################## +# +# The following function is used to get OKS info +# +############################################################## + +def SetOKSLinks( runlist ): + from utils.AtlRunQueryUtils import coolDbConn + conn = coolDbConn.GetAtlasRunDBConnection() + cursor = conn.cursor() + query = "select ConfigSchema,ConfigData from ATLAS_RUN_NUMBER.RunNumber where RunNumber=:run" + cursor.prepare(query) + for run in runlist: + cursor.execute(query, run=run.runNr) + re = cursor.fetchall() + run.addResult('oks',re[0]) + +def main(): + test_oks = True + test_sfo = False + + if test_oks: + #from os import environ as env + #from CoolRunQuery.AtlRunQueryUtils import coolDbConn + #coolDbConn.get_auth('oracle://atlr/rn_r') # only in /afs/cern.ch/atlas/project/tdaq/databases/.coral/authentication.xml + #print coolDbConn.get_auth('oracle://ATLAS_COOLPROD/ATLAS_COOLOFL_TRIGGER') + from CoolRunQuery.AtlRunQuerySFO import SetOKSLinks + SetOKSLinks([Run(178211)]) + + if test_sfo: + + runno = 122050 + import sys + if len(sys.argv)>1: + runno = int(sys.argv[1]) + + runno = [140541L, 140571L, 140579L, 140592L, 140616L, 140620L, + 140622L, 140638L, 140670L, 140682L, 140704L, 140737L, 140747L, + 140748L, 140754L, 140762L, 140765L, 140769L, 140772L, 140776L, + 140790L, 140794L, 140822L, 140836L, 140842L, 140929L, 140953L, + 140955L, 140974L, 140975L, 141046L, 141059L, 141066L, 141079L, + 141109L, 141150L, 141189L, 141192L, 141194L, 141203L, 141209L, + 141226L, 141234L, 141236L, 141237L, 141238L, 141266L, 141270L, + 141359L, 141374L, 141387L, 141398L, 141401L, 141403L, 141461L, + 141473L, 141474L, 141525L, 141527L, 141529L, 141533L, 141534L, + 141561L, 141562L, 141563L, 141565L, 141599L, 141624L, 141655L, + 141667L, 141670L, 141688L, 141689L, 141691L, 141695L, 141700L, + 141702L, 141704L, 141705L, 141706L, 141707L, 141718L, 141721L, + 141730L, 141746L, 141748L, 141749L, 141755L, 141769L, 141807L, + 141811L, 141818L, 141841L, 141915L, 141928L, 141976L, 141979L, + 141994L, 141998L, 141999L, 142042L, 142065L, 142081L, 142091L, + 142094L, 142111L, 142123L, 142125L, 142128L, 142133L, 142144L, + 142149L, 142154L, 142155L, 142157L, 142159L, 142161L, 142165L, + 142166L, 142171L, 142174L, 142183L, 142185L, 142187L, 142189L, + 142190L, 142191L, 142192L, 142193L, 142194L, 142195L, 142199L, + 142203L, 142205L, 142210L, 142214L, 142216L, 142240L, 142258L, + 142259L, 142265L, 142291L, 142301L, 142308L, 142309L, 142310L, + 142319L, 142356L, 142368L, 142383L, 142390L, 142391L, 142392L, + 142394L, 142395L, 142397L, 142400L, 142401L, 142402L, 142403L, + 142404L, 142405L, 142406L, 143019L, 143023L, 143027L, 143033L, + 143034L, 143131L, 143136L, 143143L, 143163L, 143169L, 143171L, + 143178L, 143182L, 143185L, 143190L, 143192L, 143198L, 143203L, + 143204L, 143205L, 143207L, 143210L, 143218L, 143222L, 143225L, + 143236L, 143242L] + + #runno = range(141000,143000) + + + from utils.AtlRunQueryUtils import coolDbConn + connection = coolDbConn.GetSFODBConnection() + cursor = connection.cursor() + + # find last N runs + if False: + n = 10 + runs = GetSFO_lastNruns( cursor, n ) + print 'Last %i runs:' % n + for r in runs: + print '... %i' % r + sys.exit() + + # retrieve streams + if True: + start = time() + streams = GetSFO_streamsAll( cursor, runno ) + print "streams",time()-start + start = time() + lbs = GetSFO_LBsAll ( cursor, runno ) + print "lbs",time()-start + start = time() + nev = GetSFO_NeventsAll( cursor, runno ) + print "events",time()-start + start = time() + files = GetSFO_filesAll ( cursor, runno ) + print "files",time()-start + start = time() + over = GetSFO_overlapAll( cursor, runno ) + print "overlap",time()-start + + + print "Query execution time: %f sec" % (time()-start) + + cursor.close() + connection.close() + + + +if __name__ == '__main__': + main() diff --git a/Database/CoolRunQuery/python/AtlRunQuerySelectorWorker.py b/Database/CoolRunQuery/python/AtlRunQuerySelectorWorker.py new file mode 100644 index 00000000000..80c1e8cd2a5 --- /dev/null +++ b/Database/CoolRunQuery/python/AtlRunQuerySelectorWorker.py @@ -0,0 +1,303 @@ +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + + +#import the selectors +from selector.AtlRunQuerySelectorDQ import DQSelector +from selector.AtlRunQuerySelectorTrigger import TrigKeySelector, BGSKeySelector, L1TrigKeySelector, HLTTrigKeySelector, RatesSelector, TriggerSelector +from selector.AtlRunQuerySelectorMisc import BPMSelector, LArcondSelector, DatasetsSelector, DetectorSelector, FilenameSelector, PartitionSelector, ReadyForPhysicsSelector, DurationSelector, BFieldCondition, BFieldSelector +from selector.AtlRunQuerySelectorEvents import EventSelector, AllEventsSelector, L1EventsSelector +from selector.AtlRunQuerySelectorStreams import StreamSelector +from selector.AtlRunQuerySelectorLhcOlc import LHCSelector, LHCCondition, OLCFillParamsCondition, OLCLBDataCondition, OLCLumiSelector, LuminositySelector, BeamspotSelector + +from .AtlRunQueryRun import Run + + +class SelectorWorker: + + class SelDescr: + def __init__(self,selector, priority, doesSelect, doesShow, executed): + self.selector = selector + self.priority = priority + self.doesSelect = doesSelect + self.doesShow = doesShow + self.executed = executed + + + __selectors = [] + __executedSelectorsInOrder = [] + __showSelectorsInOrder = [] + __selectorDependencies = { 'trigger':['trigkey','l1trigkey','hlttrigkey'], + 'dataquality':['readyforphysics'] + } + + __creationRules = { + 'dataquality' : 'DQSelector', + 'streams' : 'StreamSelector', + 'trigger' : 'TriggerSelector', + 'datasets' : 'DatasetsSelector', + 'rates' : 'RatesSelector', + 'lhc' : 'LHCSelector', + 'bfield' : 'BFieldSelector', + 'events' : 'EventSelector', + 'allevents' : 'AllEventsSelector', + 'release' : 'TrigKeySelector', + 'trigkey' : 'TrigKeySelector', + 'l1trigkey' : 'L1TrigKeySelector', + 'hlttrigkey' : 'HLTTrigKeySelector', + 'bgskey' : 'BGSKeySelector', + 'detmask' : 'DetectorSelector', + 'olclumi' : 'OLCLumiSelector', + 'olcfillparams' : 'OLCFillParamsCondition', + 'olclbdata' : 'OLCLBDataCondition', + 'datasets' : 'DatasetsSelector', + 'partition' : 'PartitionSelector', + 'readyforphysics' : 'ReadyForPhysicsSelector', + 'larcond' : 'LArcondSelector', + 'luminosity' : 'LuminositySelector', + 'beamspot' : 'BeamspotSelector', + 'bpm' : 'BPMSelector', + 'filenametag' : 'FilenameSelector' + } + + + @classmethod + def selectors(cls): + for s in SelectorWorker.getOrderedSelectorList(): + if s.executed: continue + needs = cls.getOrderedNeeds(s) + for ss in needs: + if ss.executed: continue + yield ss.selector + cls.__executedSelectorsInOrder += [ss] + ss.executed=True + yield s.selector + cls.__executedSelectorsInOrder += [s] + s.executed=True + + @classmethod + def executedSelectors(cls): + for s in cls.__executedSelectorsInOrder: + yield s.selector + + @classmethod + def getOrderedNeeds(cls, client): + needs = [] + selname = client.selector.name + if selname in cls.__selectorDependencies: + dependson = cls.__selectorDependencies[selname] + for d in dependson: + dep = cls.findSelectorDescriptor(d) + if dep==None: + raise RuntimeError('Selector %s depends on %s which is not defined' % (selname,d)) + needs.insert(0, dep) + return needs + + + @classmethod + def addSelector(cls, selector, priority): + s = cls.findSelectorDescriptor(selector.name) + if s: + return s.selector + cls.__selectors += [cls.SelDescr(selector=selector,priority=priority,doesSelect=True,doesShow=False,executed=False)] + return cls.__selectors[-1].selector + + + # pure selector search by class name and instance name + @classmethod + def findSelectorDescriptor(cls, selname, selcls=None): + """ returns a SelDescr if name and class match""" + for s in cls.__selectors: + if s.selector.name == selname and (selcls==None or selcls==s.selector.__class__.__name__): + return s + return None + + @classmethod + def getShowSelector(cls, selname, *args, **kwargs): + selcls = SelectorWorker.__creationRules[selname] + # see if the selector is already there + s = cls.findSelectorDescriptor(selname, selcls) + if s: + s.doesShow=True + cls.__showSelectorsInOrder += [s] + try: s.selector.addShowSelector(*args, **kwargs) + except: pass + else: + s = cls.CreateSelector( selname, True, *args, **kwargs) + cls.__showSelectorsInOrder += [s] + cls.__selectors += [s] + return s.selector + + @classmethod + def CreateSelector( cls, selname, doesShow, *args, **kwargs): + selcls = SelectorWorker.__creationRules[selname] + exec('thecls=%s' % selcls) + newsel = thecls(name=selname, *args, **kwargs) + print "CREATING SELECTOR %s %s('%s')" % (("SHOW" if doesShow else "RETRIEVE"), selcls, selname) + s = cls.SelDescr(selector=newsel, priority=0, doesSelect=False, doesShow=True, executed=False) + return s + + + @classmethod + def getRetrieveSelector(cls, selname, selcls=None, *args, **kwargs): + s = cls.findSelectorDescriptor(selname, selcls) + if s: + try: s.selector.addShowSelector() + except: pass + return s.selector + elif selcls!=None: + s = cls.CreateSelector( selname, False, *args, **kwargs) + cls.__selectors += [s] + return s.selector + else: + return None + + + @classmethod + def getOrderedSelectorList(cls): + # Highes priority first + sortedSel = cls.__selectors + sortedSel.sort(lambda x,y: y.priority-x.priority) + return sortedSel + + @classmethod + def setApplySelection(cls): + for s in cls.__selectors: + s.selector.applySelection = s.doesSelect + + @classmethod + def setShowOutput(cls): + for s in cls.__showSelectorsInOrder: + s.selector.setShowOutput() + + + @classmethod + def parseSelectorOptions(cls,options): + + if options.events: + cls.addSelector( EventSelector(name = 'events', events = options.events), 280 ) + + if options.larcond: + cls.addSelector( LArcondSelector(name = 'larcond', larcond = options.larcond), 115 ) + + if options.datasets: + cls.addSelector( DatasetsSelector(name = 'datasets', datasets = options.datasets), 110 ) + + if options.duration: + cls.addSelector( DurationSelector(name = 'duration', duration = options.duration), 95 ) + + if options.streams: + cls.addSelector( StreamSelector(name = 'streams', streams = options.streams), 95 ) + + if options.smklist: + cls.addSelector( TrigKeySelector(name = 'trigkey'), 71 ).setSelectSMK(smks = options.smklist) + + if options.release: + cls.addSelector( TrigKeySelector(name = 'trigkey'), 70 ).setSelectRelease(release = options.release) + + if options.detmaskin or options.detmaskout: + cls.addSelector( DetectorSelector('detmask', options.detmaskin, options.detmaskout), 60 ) + + if options.lhc: + cls.addSelector( LHCSelector(name = 'lhc', lhc = options.lhc), 48 ) + + if options.beamspot: + cls.addSelector( BeamspotSelector(name = 'beamspot', beamspot = options.beamspot), 44 ) + + if options.olclumi: + cls.addSelector( OLCLumiSelector(name = 'olclumi', olclumi = options.olclumi), 43 ) + + if options.bpm: + cls.addSelector( BPMSelector(name = 'bpm', release = options.bpm), 40 ) + + if options.luminosity: + cls.addSelector( LuminositySelector(name = 'luminosity', luminosity = options.luminosity), 40 ) + + if options.projecttag: + cls.addSelector( FilenameSelector(name = 'filenametag', projecttag = options.projecttag), 40 ) + + if options.readyforphysics: + cls.addSelector( ReadyForPhysicsSelector(name = 'readyforphysics', readyforphysics = options.readyforphysics), 31 ) + + if options.partition: + cls.addSelector( PartitionSelector(name = 'partition', partition = options.partition), 30 ) + + if options.trigger: + cls.addSelector( TriggerSelector(name = 'trigger', trigger = options.trigger), 25 ) + + if options.bfield: + cls.addSelector( BFieldSelector(name = 'bfield', bf = options.bfield), 10 ) + + if options.dqchannels: + cls.addSelector( DQSelector(), 50 ).addSelectionChannel(options.dqchannels) + + + + + @classmethod + def parseShowOption(cls,options): + if not options.show: return + + for s in options.show: + + if s == 'summary' or s == 'dqeff' or s == 'dqsummary' or s == 'dqplots' : pass # handled outside + + elif s=='run': Run.showrunnr = True + + elif s=='time': Run.showtime = True + + elif s=='duration': Run.showduration = True + + elif s[0:3]=='dq ' or s=='dq': + SelectorWorker.getShowSelector('dataquality').addShowChannel(s[3:]) + + elif s[0:7] == 'streams': + SelectorWorker.getShowSelector('streams').addShowStreamPattern(s[8:]) + + elif s[0:7] == 'trigger': + SelectorWorker.getShowSelector('trigger').addShowTriggerPattern(s[8:]) + + elif s[0:8] == 'datasets': + SelectorWorker.getShowSelector('datasets').addShowDatasetPattern(s[9:]) + + elif s[0:9] == 'trigrates': + SelectorWorker.getShowSelector('rates').addPattern(s[10:]) + + elif s[0:3] == 'lhc': + arg = '' + if 'all' in s: arg = 'all' + elif 'min' in s: arg = 'min' + selector = SelectorWorker.getShowSelector('lhc',addArg=arg) + + elif 'olclumi' in s: + SelectorWorker.getShowSelector(s) + + elif s == 'smk': + SelectorWorker.getShowSelector('trigkey').setShow('smk') + + elif s == 'trigkeys' or s == 'smk': + SelectorWorker.getShowSelector('trigkey').setShow('smk') + SelectorWorker.getShowSelector('l1trigkey') + SelectorWorker.getShowSelector('hlttrigkey') + SelectorWorker.getShowSelector('bgskey') + + elif s == 'release': + SelectorWorker.getShowSelector('trigkey').setShow('release') + + elif s[0:10] == 'luminosity': + SelectorWorker.getShowSelector('luminosity').addShowTag(s.split(None,1)[1]) + + elif s[0:8] == 'beamspot': + SelectorWorker.getShowSelector('beamspot',args=s[8:]) + + elif s[0:3] == 'bpm': + SelectorWorker.getShowSelector('bpm') + + elif s == 'filenametag' or s == 'projecttag': + SelectorWorker.getShowSelector('filenametag') + + elif s in ['bfield', 'events', 'allevents', 'detmask', 'olcfillparams', 'olclbdata', 'datasets', 'partition', 'readyforphysics', 'larcond']: + SelectorWorker.getShowSelector(s) + + else: + raise RuntimeError("ERROR: option 'show %s' not implemented" %s) + diff --git a/Database/CoolRunQuery/python/AtlRunQueryTier0.py b/Database/CoolRunQuery/python/AtlRunQueryTier0.py new file mode 100644 index 00000000000..8b1dc51803c --- /dev/null +++ b/Database/CoolRunQuery/python/AtlRunQueryTier0.py @@ -0,0 +1,54 @@ +#!/bin/env python + +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration +# +# ---------------------------------------------------------------- +# Script : AtlRunQueryTier0.py +# Project: AtlRunQuery +# Purpose: Utility to retrieve information from Tier0 DB +# Authors: Andreas Hoecker (CERN), Joerg Stelzer (DESY) +# Created: Nov 4, 2009 +# ---------------------------------------------------------------- +# +# Tier-0 Schema (dataset class only) +# ============================================== +# +# http://hoecker.home.cern.ch/hoecker/Tier0DatasetSchema.png +# + +import cx_Oracle +from time import time + +def GetTier0_allDatasets( cursor, runlist, dsnamepattern = [] ): + res = {} + pos = 0 + blocksize = 200 + # build dataset selection string + dsselstring = '' + for p in dsnamepattern: + p = p.replace('*','%') + dsselstring += "LOWER(DATASETNAME) like LOWER('%s') OR " % p + if dsselstring: dsselstring = 'and (' + dsselstring[:len(dsselstring)-4] + ')' + + # do selection + while pos<len(runlist): + cursor.execute("SELECT DISTINCT RUNNR,DATASETNAME,TYPE,PSTATES,DDM,NFILES,TOTSIZE,TOTEVENTS,CREATIONTIME FROM dataset WHERE RUNNR in (%s) and TYPE!='LOG' %s and not DATASETNAME like '%%.LOG%%'"% (','.join([str(i) for i in runlist[pos:pos+blocksize]]), dsselstring)) + r = cursor.fetchall() + for e in r: + res.setdefault(e[0],[]).append((e[1],e[2],e[3],e[4],e[5],e[6],e[7],e[8])) + pos += blocksize + + return res + +def GetTier0_datasetsAndTypes( cursor, runlist ): + res = {} + pos = 0 + blocksize = 200 + while pos<len(runlist): + cursor.execute("SELECT DISTINCT RUNNR,DATASETNAME,TYPE,PSTATES FROM dataset WHERE RUNNR in (%s) and TYPE!='LOG' and not DATASETNAME like '%%.LOG%%'"% ','.join([str(i) for i in runlist[pos:pos+blocksize]])) + r = cursor.fetchall() + for e in r: + res.setdefault(e[0],[]).append((e[1],e[2],e[3])) + pos += blocksize + + return res diff --git a/Database/CoolRunQuery/python/AtlRunQueryVersion.py b/Database/CoolRunQuery/python/AtlRunQueryVersion.py new file mode 100644 index 00000000000..1419a28b40f --- /dev/null +++ b/Database/CoolRunQuery/python/AtlRunQueryVersion.py @@ -0,0 +1,3 @@ +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + +SvnVersion = 'CoolRunQuery-00-04-50' \ No newline at end of file diff --git a/Database/CoolRunQuery/python/__init__.py b/Database/CoolRunQuery/python/__init__.py new file mode 100644 index 00000000000..0216ac564f8 --- /dev/null +++ b/Database/CoolRunQuery/python/__init__.py @@ -0,0 +1,7 @@ +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + + + +__author__="Andreas Hoecker, Joerg Stelzer" +__all__= ["AtlRunQueryLib","RunQueryParser"] + diff --git a/Database/CoolRunQuery/python/html/AtlRunQueryDQSummary.py b/Database/CoolRunQuery/python/html/AtlRunQueryDQSummary.py new file mode 100755 index 00000000000..2ffb8747e46 --- /dev/null +++ b/Database/CoolRunQuery/python/html/AtlRunQueryDQSummary.py @@ -0,0 +1,969 @@ +#!/usr/bin/env python + +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + +import sys, time, math +import operator + +from ROOT import * +from CoolRunQuery.AtlRunQueryQueryConfig import QC +from CoolRunQuery.selector.AtlRunQuerySelectorBase import DataKey +from CoolRunQuery.utils.AtlRunQueryUtils import importroot +importroot() + +from CoolRunQuery.output.AtlRunQueryRoot import SetStyle +from CoolRunQuery.utils.AtlRunQueryUtils import timer +from CoolRunQuery.AtlRunQueryRun import Run +from CoolRunQuery.AtlRunQueryCOMA import ARQ_COMA + +import xmlrpclib +global server +server = xmlrpclib.ServerProxy('http://atlasdqm.cern.ch') + +global readylb +global lumifolder +global usenb +global unit +global livetrigger + +######################################################################## +def MapToSystem(defect): + + # Store detectors/cp per "family" for readability + global_systems_map = {'LAR':'Calo','TILE':'Calo','CALO':'Calo', + 'TAU':'Calo','EGAMMA':'Calo','JET':'Calo','MET':'Calo', + 'ALFA':'Trigger & Lumi','LCD':'Trigger & Lumi', + 'ZDC':'Trigger & Lumi','BCM':'Trigger & Lumi', + 'LUMI':'Trigger & Lumi','TRIG':'Trigger & Lumi', + 'GLOBAL':'Trigger & Lumi', + 'MS_CSC':'Muon','MS_RPC':'Muon','MS_TGC':'Muon','MS_MDT':'Muon', + 'MS':'Muon','MBTS':'Muon','MCP':'Muon', + 'PIXEL':'Tracking','SCT':'Tracking','TRT':'Tracking', + 'ID':'Tracking','BTAG':'Tracking'} + words = defect.split('_') + for w in words: + for item in global_systems_map: + if w in item: return global_systems_map[item] + return ' ' + +######################################################################## + +def ComputeRunLumi(dic,run,lumiperlb): + # consider ATLAS ready only + global readylb + total = 0. + for l in lumiperlb: + if l not in readylb: continue + total += lumiperlb[l] + return total + +def GetLBLumi(dic,run): + global lumifolder + global usenb + + lumifolder = '' + lumiperlb = {} + for k,v in dic.items(): + if not 'ofllumi' in k.ResultKey: continue + lumifolder = k.ResultKey + if lumifolder == '': return lumiperlb + lbdic = dic[DataKey(lumifolder)][run] + lbtime = dic[DataKey('#LB')][run][1] + for i,item in enumerate(lbdic): + for lb in range(int(item.startlb),int(item.endlb)): + lumiperlb[lb] = 0. + if item.value == "n.a.": continue + if (lbtime[lb]-lbtime[lb-1])<0: continue + # change unit (pb/nb) + scale = 0.001 + if usenb: scale = 1. + lumiperlb[lb] = float(item.value)*(lbtime[lb]-lbtime[lb-1])*0.001*scale # output in nb-1/pb-1 + #print "DEBUG",run,lb,float(item.value),lbtime[lb]-lbtime[lb-1] + return lumiperlb + +def GetLBLiveFraction(dic,run): + global livetrigger + livefraction = {} + try: + #trigrate = dic[DataKey('TriggerRates')][run][0].value['L1_EM5'] + trigrate = dic[DataKey('TriggerRates')][run][0].value[livetrigger] + for item in trigrate: + #print '-----',run,item + livefraction[item[0]]= 0. + if float(item[1]) != 0: livefraction[item[0]] = float(item[3])/float(item[1]) + except: + print "%s trigger not found - no live fraction computation"%livetrigger + return livefraction + +def GetLBReady(dic,run): + rlb = [] + for b in dic[DataKey('Ready for physics')][run]: + for lb in range(b.startlb,b.endlb): + if b.value == "1" : + rlb.append(lb) + return rlb + + +######################################################################## +def MakePlot_SummaryLumiLoss(loss,colors,dicsum,name): + + global usenb + global unit + + SetStyle() + gStyle.SetTitleX(0.5); + gStyle.SetTitleAlign(23); + + ## ATLAS Ready Recorded Lumi + TotalLumi = dicsum[DataKey('TotalLumi')][0] + + ## x labels + xlabels = [s for s in loss] + + ## runs + runs = [item for item in loss[xlabels[0]]] + runs.sort() + + ## compute TOTAL lumi per system + lumi = {}; max = 0. + for sys in loss: + lumi[sys] = 0. + for r in runs: lumi[sys] += loss[sys][r] + if lumi[sys] > max: max=lumi[sys] + max = max*1.5 + + # if no entry, print no plot + if max == 0. : return + #print "DEBUG", max + + ## create frame + t1 = " Runs [%i-%i]"%(runs[0],runs[-1]) + if len(runs) == 1: t1 = " Run [%i]"%runs[0] + t2 = "ATLAS Ready Lumi: %.2f %s^{-1}"%(TotalLumi,unit) + h1_frame = TH1F("h1_frame","#splitline{%s}{%s}"%(t1,t2),len(loss),-0.5,len(loss)-0.5) + SetXLabel(h1_frame,xlabels) + h1_frame.SetMinimum(0.) + h1_frame.SetMaximum(max) + h1_frame.SetTickLength(0.,"X") + h1_frame.GetYaxis().SetTitle("Luminosity Loss during ATLAS Ready [%s^{-1}]"%unit) + h1_frame.LabelsOption("a","X") # order X label by alphabetical order + + ## create systems plots + h1_lumi = []; text_lumi = [] + for sys in loss: + h1_lumi.append(TH1F("h1_lumi_%s"%sys,"",len(loss),-0.5,len(loss)-0.5)) + SetXLabel(h1_lumi[-1],xlabels) + h1_lumi[-1].LabelsOption("a","X") # order X label by alphabetical order + h1_lumi[-1].SetBarWidth(0.90); + h1_lumi[-1].SetTickLength(0.,"X") + h1_lumi[-1].SetMinimum(0.) + h1_lumi[-1].SetMaximum(max) + h1_lumi[-1].SetFillColor(colors[sys]) + if sys == '_TOTAL': h1_lumi[-1].SetFillStyle(3244) + ibin = h1_lumi[-1].GetXaxis().FindBin(sys) + #print sys,ibin,colors[sys],lumi[sys]/TotalLumi,h1_lumi[-1].GetName() + h1_lumi[-1].SetBinContent(ibin,lumi[sys]) + if lumi[sys] > 0. : + text_lumi.append(TText(lumi[sys]*1.1,ibin-1,"%.2f %%"%(100*lumi[sys]/TotalLumi))) + else: + text_lumi.append(TText(lumi[sys]*1.1,ibin-1,"")) + + + ## create png + canvas = TCanvas( 'c_SummaryLumiLoss',"Summary Lumi Losses", 200, 10, 600, 700) + canvas.Divide(1,1) + canvas.cd(1) + gPad.SetTopMargin(0.15); + h1_frame.Draw("HBAR") + subtitle = TText(max,0.,name) + subtitle.SetTextSize(0.03) + subtitle.SetTextAngle(90) + subtitle.SetTextColor(kGray+1) + subtitle.Draw() + for i,h in enumerate(h1_lumi): + h.Draw("HBAR SAME") + text_lumi[i].SetTextAlign(12) # Vertical align + text_lumi[i].SetTextSize(0.04) # Text Size + text_lumi[i].Draw() + canvas.Update() + from CoolRunQuery.AtlRunQueryQueryConfig import QC + pname = QC.datapath+'/'+name+'_lumiloss.png' + canvas.Print(pname) + return pname + +def MakePlot_PerRunLumiLoss(loss,colors,dicsum,name): + + global usenb + global unit + + ## get runs list - remove runs with no defect + runs = [] + for r in loss['_TOTAL']: + if loss['_TOTAL'][r]>0. : runs.append(r) + if len(runs) == 0 : return + runs.sort() + + ## ATLAS Ready Recorded Lumi + TotalLumi = dicsum[DataKey('TotalLumi')][0] + + ## center histo Title + SetStyle() + gStyle.SetTitleX(0.5); + gStyle.SetTitleAlign(23); + + ## create stack + h_stack = THStack("hs_PerRun","Luminosity Loss during ATLAS Ready [%s^{-1}]"%unit); + + ## create legends + x0=0.17; y0=0.62; + nsplits = 4; dx = 0.2; dy = 0.05; + leg = TLegend(x0,y0,x0+dx,y0+nsplits*dy) + nhists = 0; + + ## settings for BAR histos + SHIFT = 0.1 + WIDTH = 0.4 + + ## create total plot + h1_total = TH1F("h1_total","",len(runs),-0.5,len(runs)-0.5) + h1_total.SetMinimum(0.) + h1_total.SetFillColor(colors['_TOTAL']) + h1_total.SetFillStyle(3244) + h1_total.SetBarWidth(WIDTH) + h1_total.SetBarOffset(SHIFT+WIDTH) + + for r,run in enumerate(runs): + if loss['_TOTAL'][run] > 0: h1_total.Fill(r,loss['_TOTAL'][run]) + nhists +=1 + leg.AddEntry(h1_total,'TOTAL',"f") + + ## create systems plots + h1_lumi = {}; text_lumi = {}; + + for i,sys in enumerate(loss): + + if sys == '_TOTAL': continue + + h1_lumi[sys] = TH1F("h1_lumi_%s"%sys,"",len(runs),-0.5,len(runs)-0.5) + h1_lumi[sys].SetMinimum(0.) + h1_lumi[sys].SetFillColor(colors[sys]) + h1_lumi[sys].SetBarWidth(WIDTH) + h1_lumi[sys].SetBarOffset(SHIFT) + for r,run in enumerate(runs): + if loss[sys][run]> 0.: h1_lumi[sys].Fill(r,loss[sys][run]) + + ## Add to stack + for sys in h1_lumi: + if h1_lumi[sys].GetEntries() > 0: + h_stack.Add(h1_lumi[sys]) + leg.AddEntry(h1_lumi[sys],sys,"f") + nhists +=1 + + ## hmax + hmax = h_stack.GetMaximum()*1.6 + + ## create frame + t1 = " Runs [%i-%i]"%(runs[0],runs[-1]) + if len(runs) == 1: t1 = " Run [%i]"%runs[0] + t2 = "ATLAS Ready Lumi: %.2f %s^{-1}"%(TotalLumi,unit) + h1_frame = TH1F("h1_frame_runs","#splitline{%s}{%s}"%(t1,t2),len(runs),-0.5,len(runs)-0.5) + SetXLabel(h1_frame,runs) + h1_frame.SetLabelSize(0.06,"X") + h1_frame.SetMinimum(0.) + h1_frame.GetYaxis().SetTitle("Luminosity Loss during ATLAS Ready [%s^{-1}]"%unit) + h1_frame.GetYaxis().SetTitleOffset(1.3) + + ## create png + canvas = TCanvas( 'c_PerRunLumiLoss',"Luminosity Losses Per Run", 200, 10, 600, 700) + canvas.Divide(1,1) + canvas.cd(1) + gPad.SetTopMargin(0.15); + h1_frame.SetMaximum(hmax) + h_stack.SetMaximum(hmax) + #h_total.SetBarOffset((shift) + + h1_frame.Draw("") + h_stack.Draw("BARsame") + h1_total.Draw("BARsame") + ncolumns = int(math.ceil(float(nhists)/float(nsplits))) + leg.SetNColumns(ncolumns) + leg.Paint() # Draw once to access coordinates + leg.SetX2NDC(leg.GetX1NDC()+ncolumns*dx) # Modify legend size w.r.t number of columns + nrows = leg.GetNRows() + leg.SetY2NDC(leg.GetY1NDC()+nrows*dy) # Modify legend size w.r.t number of rows + leg.Draw() # Draw again + + subtitle = TText(len(runs)-0.35,0.,name) + subtitle.SetTextSize(0.03) + subtitle.SetTextAngle(90) + #subtitle.SetTextColor(kGray+1) + subtitle.Draw() + + canvas.Update() + from CoolRunQuery.AtlRunQueryQueryConfig import QC + pname = QC.datapath+'/'+name+'_lumiloss_perrun.png' + canvas.Print(pname) + return pname + +def MakePlot_DefectsPerSystem(sys,intolerable,tolerable,dic,run): + + global readylb + + from CoolRunQuery.AtlRunQueryQueryConfig import QC + + hname = ['',''] + h2_DefectVsLB = ['','',''] + + ## style + SetStyle() + gStyle.SetTitleX(0.5); + gStyle.SetTitleAlign(23); + + lbrange = readylb[-1]-readylb[0] # can be disconnected ! + #print run,len(readylb),lbrange + + ## Intolerable ## + if len(intolerable) > 0 : + #TCol = TColor.GetColor( "#5D6B7D"); + TCol = TColor.GetColor( "#354355"); + hname[0]= "%s_Intolerable_Run%s"%(sys,str(dic[DataKey('Run')][run])) + htitle = "%s - Intolerable defects - Run %s"%(sys,str(dic[DataKey('Run')][run])) + h2_DefectVsLB[0] = TH2F("h2_defectVsLB_%s"%hname[0],htitle, + lbrange,readylb[0]-0.5,readylb[-1]+0.5, + len(intolerable),0.,len(intolerable)) + h2_DefectVsLB[0].SetFillColor(TCol) + h2_DefectVsLB[0].GetXaxis().SetTitle("Lumiblocks with ATLAS Ready") + SetYLabel(h2_DefectVsLB[0],[defect for defect in intolerable]) + h2_DefectVsLB[0].LabelsOption("a","Y") # order Y label by alphabetical order + + # Text to store lumi loss number + ttext = {} + # Fill & Compute lumi losses + for i,item in enumerate(intolerable): + ibiny = h2_DefectVsLB[0].GetYaxis().FindBin(item) + binxmax = h2_DefectVsLB[0].GetXaxis().GetXmax() + frac = 100*float(len(intolerable[item]))/float(len(readylb)) + if frac> 0.: ttext[item] = TText(binxmax,ibiny-0.8," %.2f %%"%frac) + else: ttext[item] = TText(binxmax,ibiny-0.8,"") + ttext[item].SetTextSize(0.03) + ttext[item].SetTextColor(TCol) + for lb in intolerable[item]: + if lb not in readylb: continue + ibinx = h2_DefectVsLB[0].GetXaxis().FindBin(lb) + h2_DefectVsLB[0].SetBinContent(ibinx,ibiny,1.) + + # create png + canvas = TCanvas( 'c_Int',"Systems Defects - %s"%hname[0], 200, 10, 1000, 800) + canvas.Divide(1,1) + canvas.cd(1) + gPad.SetLeftMargin(0.40); + gPad.SetRightMargin(0.1); + h2_DefectVsLB[0].Draw("BOX") + for item in intolerable: ttext[item].Draw() + canvas.Update() + hname[0] = QC.datapath+"/"+hname[0]+".png" + canvas.Print(hname[0]) + + ## Tolerable ## + if len(tolerable) > 0 : + + all = dict(tolerable.items() + intolerable.items()) + + TCol1 = TColor.GetColor( "#354355"); + TCol2 = TColor.GetColor("#7B899B"); + hname[1]= "%s_All_Run%s"%(sys,str(dic[DataKey('Run')][run])) + htitle = "%s - All defects - Run %s"%(sys,str(dic[DataKey('Run')][run])) + + # intolerable + h2_DefectVsLB[1] = TH2F("h2_defectVsLB_int_%s"%hname[1],htitle, + lbrange,readylb[0]-0.5,readylb[-1]+0.5, + len(all),0.,len(all)) + # tolerable + h2_DefectVsLB[2] = TH2F("h2_defectVsLB_tol_%s"%hname[1],htitle, + lbrange,readylb[0]-0.5,readylb[-1]+0.5, + len(all),0.,len(all)) + + h2_DefectVsLB[1].SetFillColor(TCol1) + h2_DefectVsLB[2].SetFillColor(TCol2) + h2_DefectVsLB[1].GetXaxis().SetTitle("Lumiblocks with ATLAS Ready") + h2_DefectVsLB[2].GetXaxis().SetTitle("Lumiblocks with ATLAS Ready") + SetYLabel(h2_DefectVsLB[1],[defect for defect in all]) + SetYLabel(h2_DefectVsLB[2],[defect for defect in all]) + h2_DefectVsLB[1].LabelsOption("a","Y") # order Y label by alphabetical order + h2_DefectVsLB[2].LabelsOption("a","Y") # order Y label by alphabetical order + + # Text to store lumi loss number + ttext = {} + + # Fill + for i,item in enumerate(all): + ibiny = h2_DefectVsLB[1].GetYaxis().FindBin(item) + binxmax = h2_DefectVsLB[1].GetXaxis().GetXmax() + frac = 100*float(len(all[item]))/float(len(readylb)) + if frac >0.: ttext[item] = TText(binxmax,ibiny-0.8," %.2f %%"%frac) + else: ttext[item] = TText(binxmax,ibiny-0.8,"") + ttext[item].SetTextSize(0.03) + if item in intolerable:ttext[item].SetTextColor(TCol1) + if item in tolerable:ttext[item].SetTextColor(TCol2) + for lb in all[item]: + ibinx = h2_DefectVsLB[1].GetXaxis().FindBin(lb) + if item in intolerable: h2_DefectVsLB[1].SetBinContent(ibinx,ibiny,1.) + if item in tolerable: h2_DefectVsLB[2].SetBinContent(ibinx,ibiny,1.) + + # create png + canvas = TCanvas( 'c_Tol',"Systems Defects - %s"%hname[1], 200, 10, 1000, 800) + canvas.Divide(1,1) + canvas.cd(1) + gPad.SetLeftMargin(0.40); + gPad.SetRightMargin(0.1); + h2_DefectVsLB[2].Draw("BOX") + h2_DefectVsLB[1].Draw("BOXSAME") + for item in all: ttext[item].Draw() + canvas.Update() + hname[1] = QC.datapath+"/"+hname[1]+".png" + canvas.Print(hname[1]) + + return hname + +######################################################################## +def SetXLabel(h,list): + for i in xrange(len(list)): + h.GetXaxis().SetBinLabel(i+1,str(list[i])) + return + +def SetYLabel(h,list): + for i in xrange(len(list)): + h.GetYaxis().SetBinLabel(i+1,str(list[i])) + return + +def listify(l): # compactify lb lists + if len(l) == 0 : return '' + i = 0; newlist=[] + while i < len(l): + start=l[i];j=i; + while(j+1<len(l) and l[j+1]==l[j]+1): j+=1 + end=l[j];i=j+1 + if start==end: newlist.append(start) + else: newlist.append(str(start)+'-'+str(end)) + return ", ".join([str(x) for x in newlist]) +######################################################################## + +class DQSummary: + + @classmethod + def makeHTML(cls, dic, dicsum, doPickle=True, doDQSummary=True, doDQPlots=True): + """ method returns a string (unicode is fine) of html code, with out tag <div>...</div> """ + + ##################################################################### + # Jessica, your main code goes here # + # (note that all methods in the class are 'static' -> @classmethod # + ##################################################################### + + ### Global Variables ### + with timer("DQSummary"): + + ### loop once over runs to decide between pb/nb### + global usenb + usenb = False + global livetrigger + livetrigger ='L1_EM30' + for r,run in enumerate(dic[DataKey('Run')]): + ptag = dic[DataKey('Project tag')][r][0].value + if 'data13' in ptag: + usenb = True + livetrigger = 'L1_EM5' + global unit + unit = 'pb' + if usenb: unit ='nb' + + ### WARNING messages + warning = '' + + ### Total Lumi : 0 = Recorded 1 = Loss (in ATLAS Ready) + dicsum[DataKey('TotalLumi')] = [0.,0.] + + ### ATLAS not Ready Total/Per run (in stable Beams) + dicsum[DataKey('TotalNotReady')] = 0. + dicsum[DataKey('TotalBusy')] = 0. + + ### Initialize Detectors Variables + detectors = ['ALFA','LCD','ZDC','BCM','PIXEL','SCT','TRT','LAR','TILE', + 'MS_CSC','MS_RPC','MS_TGC','MS_MDT','MBTS','TRIG','GLOBAL'] + detectors_color = {'ALFA':kYellow-9,'LCD':kYellow-7,'ZDC':kYellow-4, + 'BCM':kOrange+1,'PIXEL':kOrange-3,'SCT':kOrange+7,'TRT':kRed-3, + 'LAR':kRed+2,'TILE':kPink-7, + 'MS_CSC':kBlue+4,'MS_RPC':kBlue-2,'MS_MDT':kAzure+3,'MS_TGC':kBlue-9,'MBTS':kBlue-6, + 'TRIG':kGreen-3,'GLOBAL':kGreen+3,'_TOTAL':TColor.GetColor("#585858")} + detectors_lumiloss = {}; + detectors_affectedLBs = {} + + for d in detectors: + detectors_lumiloss[d]={} + detectors_affectedLBs[d]={} + + ## Additional field for TOTAL + detectors_lumiloss['_TOTAL']={} + detectors_affectedLBs['_TOTAL']={} + + ### Initialize Performances Variables + performances = ['LUMI','ID','CALO','TAU','EGAMMA','JET','MET','MCP','BTAG'] + performances_color = {'LUMI':kYellow-9,'ID':kOrange+7,'CALO':kRed+2,'TAU':kBlue+2, + 'EGAMMA':kGreen-3,'JET':kAzure+3,'MET':kGreen-6,'MCP':kBlue-3, + 'BTAG':kPink,'_TOTAL':TColor.GetColor("#585858")} + performances_lumiloss = {}; + performances_affectedLBs = {} + + for p in performances: + performances_lumiloss[p]={} + performances_affectedLBs[p]={} + + ## Additional field for TOTAL + performances_lumiloss['_TOTAL']={} + performances_affectedLBs['_TOTAL']={} + + ### Initialize table content // Considered systems ### + columns = []; global_systems = []; + columns = ['Run Info','ES1','BLK'] + global_systems = ['Calo','Tracking','Muon','Trigger & Lumi'] + columns += global_systems + + ### create big results table ### + summarytable = '<div><table id="resulttable" class="resulttable" style="margin-left: 13px; margin-right: 13px;" >' + ### create first title row ### + summarytable += '<tr>' + summarytable += '<th colspan="1"></th>' # run info column + + #summarytable += '<th colspan="2">Tier0 Reco Status</th>' + summarytable += '<th colspan="2">Missing Sign-Off</th>' + + summarytable += '<th colspan="%i">Defects in ATLAS Ready<br>'%len(global_systems) + summarytable += '<div style="font-size: xx-small; cursor: pointer;" onclick="toggle_dq(this)">[show tolerable]</div></th>' + summarytable += '</tr>' + + ### create subtitle row ### + summarytable += '<tr>' + #for item in columns: summarytable += '<th>%s</th>'%item.split(":")[0] + for item in columns: summarytable += '<th>%s</th>'%item + summarytable += '</tr>' + + ### loop over runs ### + for r,run in enumerate(dic[DataKey('Run')]): + + ## Get list of ATLAS Ready LBs + global readylb + readylb = GetLBReady(dic,r) + + ## If no ATLAS ready LB: skip the run + if len(readylb) == 0: + warning += '<center><font color="red">WARNING! Run %s has 0 ATLAS Ready LB</font></center><br>'%run + continue + + ## Get lumi per LB and live fraction for the current run + lumiperlb = GetLBLumi(dic,r) + livefrac = GetLBLiveFraction(dic,r) + + if len(lumiperlb) == 0: + warning += '<center><font color="red">WARNING! Run %s has no offline lumi info</font></center><br>'%run + continue + + ## Correct lumi per LB with live fraction + #print 'DEBUG',run,len(lumiperlb),len(livefrac),len(readylb) + for l in lumiperlb: + # IF statement used when len(lumiperlb)!= len(livefrac) + if l not in readylb: continue + if l not in livefrac: + print "--> Warning: live fraction not available for LB %i. Setting live fraction to 1."%l + else: + lumiperlb[l]*=livefrac[l] + + ## Initialize columns content for current run + content = {} + for i,item in enumerate(columns):content[item]='' + + ## Retrieve and store run info + dp = '?'; ptag = dic[DataKey('Project tag')][r][0].value + year = '20'+ptag.split('_')[0][4:] + DP = ARQ_COMA.get_periods_for_run(run) + if len(DP)>0: dp=DP[0] + content['Run Info'] = '<b>%s</b> <font style="font-size:10px;">data period <b>%s</b></font><br>'%(str(run),dp) + + ### In case some systems should be removed + tobeignored = ['ALFA','ZDC','LCD'] + if 'data13' in ptag: tobeignored = [] + + ## useful links + target='target="blank"' + somelinks = '<font style="font-size:10px;color:#0000FF">' + somelinks += '<a href="https://atlas-tagservices.cern.ch/tagservices/RunBrowser/runBrowserReport/runBrowserReport.php?runs=%s&pn=%s&fnt=%s" %s>COMA</a>'%(run,dp,ptag,target) + somelinks += ', <a href="https://atlasdqm.web.cern.ch/atlasdqm/DQBrowser/makeMatrix.php?selection=All&run=%s&runup=&dbinstance=COMP200_DCS&tag=DEFAULT" %s>DCS</a>'%(run,target) + somelinks += ', <a href="https://atlas.web.cern.ch/Atlas/GROUPS/DATAPREPARATION/DataSummary/%s/run.py?run=%s" %s>DS</a>'%(year,run,target) + somelinks += ', <a href="https://atlasdqm.cern.ch/defectentry/?run=%s" %s>defects</a>'%(run,target) + somelinks += '<br><a href="https://atlasdqm.cern.ch/dqsignoff/%s/" %s>DQlogbook</a>'%(run,target) + somelinks += ', <a href="https://atlasdqm.cern.ch/webdisplay/tier0?lowrun=%s&highrun=%s&show_all=on" %s>DQWebDisplay</a>'%(run,run,target) + somelinks += '</font><br>' + + content['Run Info'] += somelinks + + ## Start and End time + rundate = dic[DataKey('Start and endtime')][r].split(",") + tstart = time.strftime("%a, %d %b %Y %H:%M:%S",time.localtime(float(rundate[0]))) + tend = time.strftime("%a, %d %b %Y %H:%M:%S",time.localtime(float(rundate[1]))) + content['Run Info'] += '<font style="font-size:10px;">' + content['Run Info'] += '<br>Start: <font style="color:#4C7D7E;">%s</font>'%tstart + content['Run Info'] += '<br>End: <font style="color:#4C7D7E;">%s</font>'%tend + content['Run Info'] += '</font>' + + #nevents = str(dic[DataKey('#Events')][r][0].value) + #content['Run Info'] += 'Recorded events: '+nevents+'<br>' + #content['Run Info'] += 'Number of LBs : '+dic[DataKey('#LB')][r][0]+'<br>' + #content['Run Info'] += 'LHC Fill : %s<br>'%(dic[DataKey('lhc:fillnumber')][r][0].value)+'<br>' + + ## Add stable Beam / ATLAS Ready info in lhctable + lhctable = '<br><font style="font-size:10px;">' + lhctable += '<table><tr><td>' + lhctable += '<b>Stable Beam</b><br>%s'%'<br>'.join([ '%s-%s: <font style="color:#4C7D7E">%s</font>'%(b.startlb,b.endlb,b.value) for b in dic[DataKey('lhc:stablebeams')][r]]) + lhctable += '</td><td>' + lhctable += '<b>ATLAS Ready</b><br>%s'% '<br>'.join([ '%s-%s: <font style="color:#4C7D7E">%s</font>'%(b.startlb,b.endlb,b.value) for b in dic[DataKey('Ready for physics')][r]]) + lhctable += '</td></tr></table>' + lhctable += '</font>' + content['Run Info'] += lhctable + + ## Add Total Lumi + lumitot = ComputeRunLumi(dic,r,lumiperlb) + dicsum[DataKey('TotalLumi')][0]+= lumitot + content['Run Info'] += '<font style="font-size:10px;">' + content['Run Info'] += 'Ready Recorded: <b>%.2f</b> %s<sup>-1</sup>'%(lumitot,unit) + content['Run Info'] += '</font><br>' + + ## Retrieve DQ defects + defects = dic[DataKey('DQ')][r] + ## Select only primary flags + defects_primary = [d for d in defects if d.value.primary] + + ## Initialize list of affected LBs per detector/cp + ## Initialize lumi losses due to intolerable defects + for d in detectors: + detectors_lumiloss[d][run]= 0. + detectors_affectedLBs[d][run]=[] + for p in performances: + performances_lumiloss[p][run]=0. + performances_affectedLBs[p][run]=[] + + # Total per run + detectors_lumiloss['_TOTAL'][run]= 0. + detectors_affectedLBs['_TOTAL'][run]=[] + performances_lumiloss['_TOTAL'][run]= 0. + performances_affectedLBs['_TOTAL'][run]=[] + + # And the big sums + total_affectedLBs = [] + GlobalNotReady = []; GlobalBusy = []; + + ## Store list of defects and affected LBs to print systems table + ListOfIntolerableDefects = {}; ListOfTolerableDefects = {}; + + ## Loop over defects + for item in defects_primary: + defect = item.value.defect + comment = item.value.comment + user = item.value.user + startlb = item.startlb + endlb = item.endlb + tolerable = item.value.tolerable + system = MapToSystem(defect) + + if 'GLOBAL_NOTREADY' in defect: + GlobalNotReady += [ lb for lb in range(startlb,endlb)] + + if 'GLOBAL_BUSY' in defect: + GlobalBusy += [ lb for lb in range(startlb,endlb) if lb in readylb] + + ## Missing Sign-Offs ## + + ## FINAL sign-off + if 'UNCHECKED_FINAL' in defect: continue + + ## BULK sign-off + if 'BULK_UNCHECKED' in defect: + short = defect.split('_UNCHECKED')[0] + content['BLK']+='<font style="font-size:8px;font-weight:bold;">%s</font><br>'%short + continue + ## ES sign-off + if 'UNCHECKED' in defect: + short = defect.split('_UNCHECKED')[0] + content['ES1']+='<font style="font-size:8px;font-weight:bold;">%s</font><br>'%short + continue + + ## Some cross-checks + if system =='': + print 'run %s: this defect is fishy %s '%(run,defect) + continue + + ## Some cross-checks + word = defect.split('_'); cpdet = word[0] + if word[0]=='MS' :cpdet += "_"+word[1] # MS systems + if not cpdet in detectors and not cpdet in performances: + print 'This system is not included: %s (%s)'%(cpdet,defect) + continue + + ## Store intolerable defects if in ATLAS Ready LBs only + if not tolerable: + RangeDefect = [ lb for lb in range(startlb,endlb) if lb in readylb ] + if len(RangeDefect)>0: + if not defect in ListOfIntolerableDefects: + ListOfIntolerableDefects[defect]= [[],''] + ListOfIntolerableDefects[defect][0] = RangeDefect + ListOfIntolerableDefects[defect][1] = user+':'+comment + else: + ListOfIntolerableDefects[defect][0]+= RangeDefect + if comment not in ListOfIntolerableDefects[defect][1]: + ListOfIntolerableDefects[defect][1]+= ' '+user+':'+comment + # This is used to compute lumilosses + # Here, we do not include systems "to be ignored" + if cpdet in tobeignored: continue + # Store list of affected LBs per detector/cp group + # we can have a double counting of LBs if defects overlap - fixed later + if cpdet in detectors: detectors_affectedLBs[cpdet][run]+=RangeDefect + if cpdet in performances: performances_affectedLBs[cpdet][run]+=RangeDefect + total_affectedLBs += RangeDefect + + ## Store tolerable defects + else: + RangeDefect = [ lb for lb in range(startlb,endlb) if lb in readylb] + if len(RangeDefect)>0: + if not defect in ListOfTolerableDefects: + ListOfTolerableDefects[defect]= [[],''] + ListOfTolerableDefects[defect][0]= RangeDefect + ListOfTolerableDefects[defect][1]= '[%s]:%s '%(user,comment) + else: + ListOfTolerableDefects[defect][0]+= RangeDefect + if comment not in ListOfTolerableDefects[defect][1]: + ListOfTolerableDefects[defect][1] += '[%s]:%s '%(user,comment) + ## end of defects loop ## + + ## Create defects table for each system + for item in global_systems: + content[item]='<table class="dqdefects">' + + for item in ListOfIntolerableDefects: + system = MapToSystem(item) + if not system in global_systems: continue + lbs = listify(ListOfIntolerableDefects[item][0]) + tip = ListOfIntolerableDefects[item][1] + tipkey = "dqcomment_int_%s_%s"%(item,run) + Run.addGlobalToolTip(tipkey,tip) + content[system]+='<tr class="showTip %s" >'%(tipkey) + content[system]+='<td class="intolerable">%s</td>'%(item) + content[system]+='<td class="lb">%s</td>'%(lbs) + content[system]+='</tr>' + + for item in ListOfTolerableDefects: + system = MapToSystem(item) + if not system in global_systems: continue + lbs = listify(ListOfTolerableDefects[item][0]) + tip = ListOfTolerableDefects[item][1] + tipkey = "dqcomment_tol_%s_%s"%(item,run) + Run.addGlobalToolTip(tipkey,tip) + content[system]+='<tr class="showTip %s tolerable" style="visibility:collapse;">'%(tipkey) + content[system]+='<td>%s</td>'%(item) + content[system]+='<td class="lb">%s</td>'%(lbs) + content[system]+='</tr>' + + ## close systems defects tables + for item in global_systems: + content[item]+='</table><br>' + + ## Add defects plots in each system column + thumbsize = 70 + imgsize = 600 + for sys in global_systems: + tol = {}; int = {}; + for defect in ListOfTolerableDefects: + # remove systems to be ignored from the plots + word = defect.split('_'); cpdet = word[0] + if word[0]=='MS' :cpdet += "_"+word[1] # MS systems + if cpdet in tobeignored: continue + if sys==MapToSystem(defect): tol[defect]=ListOfTolerableDefects[defect][0] + for defect in ListOfIntolerableDefects: + # remove systems to be ignored from the plots + word = defect.split('_'); cpdet = word[0] + if word[0]=='MS' :cpdet += "_"+word[1] # MS systems + if cpdet in tobeignored: continue + if sys==MapToSystem(defect): int[defect]=ListOfIntolerableDefects[defect][0] + + hname = MakePlot_DefectsPerSystem(sys,int,tol,dic,r) + for h in hname: + if len(h)== 0 : continue + title = "Click to zoom" + wincontent = "<img src="%s" height=%s/>"%(h,imgsize) + openwin = "javascript:openLargeWindow('Print1','" + openwin += "<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">" + openwin += "<html xmlns:"my"><head><title>Defects for run %s</title></head>"%run + openwin += "<body style="background-color:#ffffff">%s</body></html>"%wincontent + openwin += "')" + content[sys]+='<a title="%s" href="%s" ><img src="%s" height=%s/></a>'%(title,openwin,h,thumbsize) + + ## Compute Global not ready ## + l = 0.; + for lb in GlobalNotReady: l += lumiperlb[lb] + dicsum[DataKey('TotalNotReady')]+= l + content['Run Info'] += '<font style="font-size:10px;">' + content['Run Info'] += 'Global Not Ready: <b>%.2f</b> %s<sup>-1</sup>'%(l,unit) + content['Run Info'] += '</font><br>' + + ## Compute Global Busy ## + l = 0.; + for lb in GlobalBusy: l += lumiperlb[lb] + dicsum[DataKey('TotalBusy')]+= l + content['Run Info'] += '<font style="font-size:10px;">' + content['Run Info'] += 'Global Busy: <b>%.2f</b> %s<sup>-1</sup>'%(l,unit) + content['Run Info'] += '</font><br>' + + ## Compute cp/det lumilosses for current run + for d in detectors: + if len(detectors_affectedLBs[d][run]) == 0: continue + # Remove double counting (defects overlap) + dll = list(set(detectors_affectedLBs[d][run])) + detectors_affectedLBs['_TOTAL'][run]+= dll + for lb in dll: + detectors_lumiloss[d][run]+= lumiperlb[lb] + for p in performances: + if len(performances_affectedLBs[p][run]) == 0: continue + # Remove double counting (defects overlap) + pll = list(set(performances_affectedLBs[p][run])) + performances_affectedLBs['_TOTAL'][run]+= pll + for lb in pll: + performances_lumiloss[p][run]+= lumiperlb[lb] + + ## Compute total Lumiloss for this run + ## Remove double counting (defects overlap) + detectors_affectedLBs['_TOTAL'][run]=list(set(detectors_affectedLBs['_TOTAL'][run])) + performances_affectedLBs['_TOTAL'][run]=list(set(performances_affectedLBs['_TOTAL'][run])) + total_affectedLBs = list(set(total_affectedLBs)) + totallossperrun = 0. + # Store the values + for lb in total_affectedLBs: + totallossperrun += lumiperlb[lb] + dicsum[DataKey('TotalLumi')][1] += totallossperrun + # Store the values + for lb in detectors_affectedLBs['_TOTAL'][run]: + detectors_lumiloss['_TOTAL'][run] += lumiperlb[lb] + # Store the values + for lb in performances_affectedLBs['_TOTAL'][run]: + performances_lumiloss['_TOTAL'][run] += lumiperlb[lb] + + ## Add Total LumiLoss in run info column + content['Run Info'] += '<font style="font-size:10px;">' + content['Run Info'] += 'DQ Lumi Loss: <b>%.2f</b> %s<sup>-1</sup>'%(totallossperrun,unit) + content['Run Info'] += '</font><br>' + + ### Print run row ### + summarytable+='<tr class="out2">' + for item in columns: summarytable+='<td>%s</td>'%content[item] + summarytable+='</tr>' + + ### end of run loop ### + ### end of results table ### + summarytable += '</table></div><br>' + + ########################## + ## Create Summary Plots ## + ########################## + + summaryplot = '' + + from CoolRunQuery.AtlRunQueryQueryConfig import QC + + plots = [] + + plots.append(MakePlot_SummaryLumiLoss(detectors_lumiloss,detectors_color,dicsum,'detectors')) + plots.append(MakePlot_SummaryLumiLoss(performances_lumiloss,performances_color,dicsum,'performances')) + plots.append(MakePlot_PerRunLumiLoss(detectors_lumiloss,detectors_color,dicsum,'detectors')) + plots.append(MakePlot_PerRunLumiLoss(performances_lumiloss,performances_color,dicsum,'performances')) + + imgsize=500 + thumbsize=300 + title = "Click to zoom" + + ## Start plots table at the top of the page + summaryplot += '<table align="center" width="90%">' + summaryplot += '<th>' + + for p in plots: + + wincontent = "<img src="%s" width=%s/>"%(p,imgsize) + + #openwincmd = "javascript:openLargeWindow('Print%i','"%(r) + openwincmd = "javascript:openWindow('Print%i','"%(run) + openwincmd += "<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">" + openwincmd += "<html xmlns:"my"><head><title>DQ Summary Plot - Run %s</title></head>"%(run) + openwincmd += "<body style="background-color:#ffffff">%s</body></html>"%wincontent + openwincmd += "')" + + summaryplot+='<td><a title="%s" href="%s" ><img src="%s" height=%s/></a></td>'%(title,openwincmd,p,thumbsize) + + ## End plots table + summaryplot += '</th></table>' + + ## debug ## + #s = "<div>%s</div>" % '<br>'.join([ "%s: %s" % (k.ResultKey,v) for k,v in dicsum.items()]) + #s = "<div>%s</div>" % '<br>'.join([ "%s: %s" % (k.ResultKey,v) for k,v in dic.items()]) + #print s + + ########################### + ## Print Summary numbers ## + ########################### + + print '+++++++++++++++++++++++++ Summary +++++++++++++++++++' + print ' Total Ready Recorded Luminosity: %.2f %s-1'%(dicsum[DataKey('TotalLumi')][0],unit) + print ' Total Luminosity Loss (ATLAS Ready) : %.2f %s-1'%(dicsum[DataKey('TotalLumi')][1],unit) + print ' Total Global Not Ready (Stable Beams): %.2f %s-1'%(dicsum[DataKey('TotalNotReady')],unit) + print ' Total Global Busy (Stable Beams): %.2f %s-1'%(dicsum[DataKey('TotalBusy')],unit) + print '+++++++++++++++++++++++++++++++++++++++++++++++++++++' + + global lumifolder + + summaryinfo = '<table align="center" style="font-size:80%;"><tr>' + summaryinfo +='<td> Total Luminosity Loss: <b>%.2f %s<sup>-1</sup></b> (%.2f%%)'%(dicsum[DataKey('TotalLumi')][1],unit,float(dicsum[DataKey('TotalLumi')][1])*100/float(dicsum[DataKey('TotalLumi')][0])) + summaryinfo +='<br>Excluded Systems: %s</td></tr>'%tobeignored + summaryinfo +='<tr><td style="font-size:70%%">using %s // %s</td></tr>'%(lumifolder.split(':')[2],livetrigger) + summaryinfo +='</table>' + + ############################# + ## return web page content ## + ############################# + + pagecontent = summaryplot +'<br>' + pagecontent += summaryinfo +'<br>' + if doDQSummary: pagecontent += summarytable + pagecontent += warning + return pagecontent + + + + + + + + + + + + + + + + +if __name__ == "__main__": + # for standalone tests (without querying) just run this and view under test.html + + import pickle + from CoolRunQuery.AtlRunQueryQueryConfig import QC + QC.datapath = "data" + + pageinfo = pickle.load(open('%s/dqsum_pi.pickle' % QC.datapath)) + + from CoolRunQuery.html.AtlRunQueryHTML import ResultPageMaker as RPM + RPM.makePage(pageinfo, doDQPickle=False) + + diff --git a/Database/CoolRunQuery/python/html/AtlRunQueryHTML.py b/Database/CoolRunQuery/python/html/AtlRunQueryHTML.py new file mode 100644 index 00000000000..4bea9ae496f --- /dev/null +++ b/Database/CoolRunQuery/python/html/AtlRunQueryHTML.py @@ -0,0 +1,377 @@ +#!/bin/env python + +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration +# +# ---------------------------------------------------------------- +# Script : AtlRunQueryHTML.py +# Project: AtlRunQuery +# Purpose: Library with HTML code creation +# Authors: Andreas Hoecker (CERN), Joerg Stelzer (DESY) +# Created: Apr 19, 2010 +# ---------------------------------------------------------------- +# +from __future__ import with_statement + +import sys, re, time, datetime, os +from random import choice + +from CoolRunQuery.AtlRunQueryRun import Run + +from CoolRunQuery.utils.AtlRunQueryUtils import addKommaToNumber, filesize +from CoolRunQuery.utils.AtlRunQueryTimer import timer + +from .AtlRunQueryPageMaker import PageMaker as PM +from CoolRunQuery.selector.AtlRunQuerySelectorBase import DataKey + + +class ResultPageMaker: + + @classmethod + def makePage(cls, pageinfo, doDQPickle=True): + + # turn dict pageinfo into local variables + for x in pageinfo.keys(): + exec("%s = pageinfo['%s']" % (x,x) ) + + # put in link to cache + cachelink = datapath[datapath.rfind('arq_'):].rstrip('/') + cachelinkline = '[ <a style="font-size:11px;font-weight:100;font-style:normal" href="query.py?q=%s"><i>cached link to this result page</i></a> ]' % cachelink + + extraReplace = [ ('<!--INSERTLINKCACHE-->', cachelinkline) ] + + # START WITH THE PAGE + body = u'' + + # overall summary output if requested + if makeSummary: + body += cls._OverallSummary( pageinfo ) + + # overall summary output if requested + if makeDQSummary or makeDQPlots: + body += cls._DQSummaryWrap( pageinfo, doPickle=doDQPickle ) + extraReplace += [ ('<!--CONTACT_START-->.*<!--CONTACT_END-->', '<a href="http://consult.cern.ch/xwho/people/572921">Jessica Leveque</a>') ] + + # initial information (query, selection steps, totNEv) + pageinfo['selstr'] = cls._makeSelectionSteps( pageinfo ) + body += cls._InitialSummary( pageinfo ) + + if makeDQSummary or makeDQPlots: + # only the global tooltips are needed + body += cls._defineToolTips( pageinfo, globalOnly=True ) + else: + # horizontal line + body += '<hr style="width:95%; background-color: #999999; height:1px; margin-left:14px; border:0">\n<p>\n' + + # results table + pageinfo['sumstr'] = cls._makeYellowLine( pageinfo ) # the yellow summary line + body += cls._makeResultsTable( pageinfo ) + + # tooltips + body += cls._defineToolTips( pageinfo ) + + # bottom table + body += '<p>\n' + body += cls._makeBottomTable(pageinfo) + + PM.makePage(body, origQuery, extraReplace=extraReplace, removeExamples=makeDQSummary or makeDQPlots) + + + + @classmethod + def _makeResultsTable( cls, pageinfo ): + for x in pageinfo.keys(): + exec("%s = pageinfo['%s']" % (x,x) ) + + # run results table + resultstable = '<table class="resulttable" id="resulttable" style="margin-left: 13px">\n' + + # table head + resultstable += Run.header() + + # runs + with timer("print the runs"): + for r in runlist: + with timer("print run %i" % r.runNr): + resultstable += str(r) + + # summary + headlist = Run.headerkeys() + resultstable += " <tr class=\"space\"><td style=\"text-align: left;\" colspan=\"%i\"><font size=-2><i>Summary</i>:</font></td></tr>\n" % (len(headlist)-1) + resultstable += " <tr class=\"sum\">" + sumstr + "</tr>\n" + resultstable += "</table>\n" + return resultstable + + + @classmethod + def _defineToolTips( cls, pageinfo, globalOnly=False ): + for x in pageinfo.keys(): + exec("%s = pageinfo['%s']" % (x,x) ) + tooltips = u'' + with timer("print the tooltips"): + dw_call = ['<script type="text/javascript">'] + dw_call += ["if(dw_Tooltip.content_vars==undefined) {dw_Tooltip.content_vars = {}; };"] + dw_call += Run.GlobalTooltips + if not globalOnly: + for r in runlist: + dw_call += r.tooltips + dw_call += ['</script>'] + for l in dw_call: + tooltips += unicode(l) + '\n' + return tooltips + + + + # makes the bottom table (between yellow line and footer + @classmethod + def _makeBottomTable( cls, pageinfo ): + for x in pageinfo.keys(): + exec("%s = pageinfo['%s']" % (x,x) ) + + bottomTable = '<table cellspacing="0" style="color: #777777; font-size: 80%; margin-left: 13px">\n' + bottomTable += '<tr>\n<td valign="top">\n' + bottomTable += cls._Description() + bottomTable += '<tr><td colspan="2"><p><hr style="width:40%; color:#999999; background-color: #999999; height:1px; margin-left:0; border:0"/>\n<p>\n</td></tr>' + + # XML output (official format for input to good-run lists) + bottomTable += cls._XMLFormat(datapath, xmlfilename, xmlhtmlstr) + + # pickled output + bottomTable += cls._PickledOutput(datapath) + + # ROOT output + bottomTable += cls._RootOutput(roothtmlstr, datapath) + + # end of bottom table + bottomTable += '<tr><td colspan="2"><p>\n<p>\n</td></tr>' + bottomTable += '</table>' + return bottomTable + + + # create DQ efficiency summary + @classmethod + def _makeDQeff( cls, pageinfo ): + for x in ["dicsum", "dic", "datapath", "makeDQeff"]: + exec("%s = pageinfo['%s']" % (x,x) ) + + if dicsum and makeDQeff: + with timer("make the DQ efficiency summary"): + from CoolRunQuery.utils.AtlRunQueryDQUtils import MakeDQeffHtml + return MakeDQeffHtml( dic, dicsum, datapath ) + else: + return '' + + # build the yellow summary line + @classmethod + def _makeYellowLine( cls, pageinfo ): + for x in ["dicsum"]: + exec("%s = pageinfo['%s']" % (x,x) ) + with timer("make the yellow summary line"): + sumdic = {} + for key, summary in dicsum.items(): + if key.Type == DataKey.STREAM: + s = addKommaToNumber(summary[0])+' <BR><font size="-2">(%s)</font>' % filesize(summary[1]) + elif key=='Run': + s = addKommaToNumber(summary) + " runs" + else: + s = addKommaToNumber(summary) + sumdic[key.ResultKey] = s.strip() + sumstr = '' + headlist = Run.headerkeys() + for title in headlist: + sumstr += '<td style="text-align:right;">%s</td>' % (sumdic[title] if title in sumdic else '') + return sumstr + + + # selection steps + @classmethod + def _makeSelectionSteps( cls, pageinfo ): + for x in ["selout"]: + exec("%s = pageinfo['%s']" % (x,x) ) + selstr = '<table style="color: #777777; font-size: 100%" cellpadding="0" cellspacing="0">' + for sel in selout: + if 'SELOUT' == sel[0:6]: + selout = sel[7:].split('==>') + if len(selout)==1: + selstr += '<tr><td width="400" style="vertical-align:top">%s</td></tr>' % selout[0] + else: + selstr += '<tr><td width="400" style="vertical-align:top">%s</td><td width="20" style="vertical-align:top">:</td><td style="vertical-align:top">%s</td></tr>' % tuple(selout[0:2]) + selstr += '</table>' + return selstr + + + + + @classmethod + def _prettyNumber( cls, n, width=-1, delim=',',decimal='.' ): + """Converts a float to a string with appropriately placed commas""" + if width >= 0: s = "%.*f" % (width, n) + else: s = str(n) + dec = s.find(decimal) + if dec == -1: dec = len(s) + threes = int((dec-1)/3) + for i in xrange(threes): + loc = dec-3*(i+1) + s = s[:loc] + delim + s[loc:] + return s + + + @classmethod + def _OverallSummary( cls, pageinfo ): + for x in pageinfo.keys(): + exec("%s = pageinfo['%s']" % (x,x) ) + with timer("make the summary"): + from CoolRunQuery.html.AtlRunQuerySummary import MakeSummaryHtml + overallsummarystr = MakeSummaryHtml( dic, dicsum, datapath ) + if overallsummarystr == '': return '' + s = '''<table width="95%" cellpadding="5" style="margin-left: 13px"> + <tr><td colspan=2 bgcolor=gainsboro><font size=+1><b>Search Result Summary</b></font></td></tr> + </table>''' + s += '<p></p>' + s += overallsummarystr + s += '<p></p>' + return s + + + @classmethod + def _DQSummaryWrap( cls, pageinfo, doPickle=True ): + from CoolRunQuery.utils.AtlRunQueryUtils import runsOnServer + if not runsOnServer() and doPickle: + import pickle + from CoolRunQuery.AtlRunQueryQueryConfig import QC + f = open('%s/dqsum_pi.pickle' % QC.datapath, "w") + pickle.dump(pageinfo, f) + f.close() + for x in pageinfo.keys(): + exec("%s = pageinfo['%s']" % (x,x) ) + with timer("make the DQ summary"): + from CoolRunQuery.html.AtlRunQueryDQSummary import DQSummary + dqsummary = DQSummary.makeHTML( dic, dicsum, doDQSummary=makeDQSummary, doDQPlots=makeDQPlots ) + if dqsummary == '': return '' + s = '''<table width="95%" cellpadding="5" style="margin-left: 13px"> + <tr><td colspan=2 bgcolor=gainsboro><font size=+1><b>Data Quality Summary</b></font></td></tr> + </table>''' + s += '<p></p>' + s += dqsummary + s += '<p></p>' + return s + + + @classmethod + def _InitialSummary(cls, pageinfo): + for x in pageinfo.keys(): + exec("%s = pageinfo['%s']" % (x,x) ) + error = False + totEv,naEv = Run.totevents[0:2] + s_table = '''<table width="95%" cellpadding="5" style="margin-left: 13px"> + <tr><td colspan=2 bgcolor=gainsboro><font size=+1><b>Search Result</b></font></td></tr> + </table>''' + s_table += '<table width="95%" cellpadding="0" cellspacing="3" style="font-size: 90%; margin-left: 13px">\n' + s_table += '<tr height="10"></tr>\n' + s_table += '<tr><td height="10" width="130" style="vertical-align:top"><i>Selection rule:</i></td><td width=10></td><td valign=top>%s</td></tr>\n' % origQuery + if 'erbose' in fullQuery: + s_table += '<tr><td height="10" style="vertical-align: top; color: #777777"><i>Query command:</i></td><td width=10 style="color: #777777"></td><td style="color: #777777">' + s_table += """<a href="javascript:animatedcollapse.toggle('AtlRunQueryCmd')">""" + s_table += '<font color="#777777">[ Click to expand/collapse command... ]</font></a>' + s_table += '<div id="AtlRunQueryCmd" style="width: 100%; background: #FFFFFF; color: #777777; display:none">' + s_table += '%s' % (fullQuery) + s_table += '</div></td></tr>\n' + s_table += '<tr><td height="10" style="vertical-align: top; color: #777777"><i>Selection sequence:</i></td><td width=10 style="color: #777777"></td><td style="vertical-align: top">%s</td></tr>\n' % (selstr) + if not error: + s_table += '<tr><td height="10" style="vertical-align: top"><i>No. of runs selected:</i></td><td></td><td valign="top">%s</td></tr>\n' % len(runlist) + if totEv >= 0: + if naEv >= 0: + sr = 'run' + if naEv > 1: sr += 's' + if naEv == 0: + s_table += '<tr><td height="10" valign="top"><i>Total no. of events:</i></td><td></td><td valign="top">%s</td></tr>\n' % (cls._prettyNumber(totEv)) + else: + s_table += '<tr><td height="10" valign="top"><i>Total no. of events:</i></td><td></td><td valign="top">%s (excluding %i %s without available "#Events" information)</td></tr>\n' % (cls._prettyNumber(totEv), naEv, sr) + else: + s_table += '<tr><td height="10" valign="top"><i>Total no. of events:</i></td><td></td><td valign="top">%s</td></tr>\n' % (cls._prettyNumber(totEv)) + s_table += '<tr><td height="10" valign="top"><i>Execution time:</i></td><td></td><td valign="top">%.1f sec</td></tr>\n' % round(querytime,1) + s_table += '<tr><td height=5 valign=top></td></tr>\n' + s_table += '</table>' + return s_table + + + @classmethod + def _PickledOutput(cls, datapath): + return '''<tr><td style="vertical-align: top"><img vspace=0 src="images/download.gif"> </td><td> + <a href="./%s/atlrunquery.pickle" target=_blank title="Query results as serialised python dictionary"> + Result table as pickled <b>python dictionary</b> (right-click link to download)</a> + <br> + use as follows:<br> + − + open file (f) in python and deserialise ("unpickle") + via: dico = pickle.load(f)<br> − the dictionary keys are equal to the column titles, + the lines are stored as list entries for each key + </td></tr>''' % datapath + + + @classmethod + def _RootOutput(cls, roothtmlstr, datapath): + if roothtmlstr!=None: + return ''' + <tr><td style="vertical-align: top"><img vspace=0 width="17" src="images/tree_icon.gif"> </td><td> + <a href="./%s/atlrunquery.root" title="Query results as ROOT TTree">Result table as <b>TTree</b> in ROOT file (right-click link to download)</a></td></tr> + <tr><td style="vertical-align: top"><img vspace=0 src="images/statistics.gif"> </td><td style="vertical-align: top"> + <font color="#333333"><b>Graphs</b> of table columns versus run numbers:</font> + </td></tr> + <tr><td style="vertical-align: top"></td><td style="vertical-align: top">%s + </td></tr>''' % (datapath, roothtmlstr) + else: + return ''' + <tr><td style="vertical-align: top"><img vspace=0 width="17" src="images/tree_icon.gif"> </td><td style="color: #000064;"> + Not available. <font color="#333333">The creation of a ROOT file with the results has been disabled by default. Please use option <b><i>root</i></b> as shown in the examples under <font style="color:red">Xtras</font></font></td></tr> + <tr><td style="vertical-align: top"><img vspace=0 src="images/statistics.gif"> </td><td style="vertical-align: top"> + <font color="#333333">The creation of <b>Graphs</b> has been disabled by default. Please use option <b><i>root</i></b> as shown in the examples under <font style="color:red">Xtras</font></font> + </td></tr>''' + + + @classmethod + def _Description(cls): + return '''<tr style="line-height: 6.5"><td colspan="2"></td></tr> + <tr><td style="vertical-align: top"><img vspace=0 src="images/info.gif"> </td><td height="55"><i>n.a.</i> + in the table stands for <i>not available</i>:<br> − + in case of <i>#Events</i> this mostly indicates unproperly closed runs<br> − + in case of <i>data quality flags</i> it indicates that the corresponding flag was not set<br> − + <i><font color="red">runs in red</font></i> indicate that their selection is questionable due to n.a. information + (n.a. fields pass the corresponding selection query) + </td></tr>''' + + + @classmethod + def _PrintFormat(cls, s_table, resstr): + s = '<tr><td><img vspace=0 src="images/printtopic.gif"> </td>\n' + s += '<td><a href="javascript:openWindow(' + s += "'Print','<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html xmlns:"my"><head><title>Run query result</title><LINK href="atlas-runquery.css" rel="stylesheet" type="text/css"><body><table style="font-family: sans-serif; font-size: 85%%">" + s += '<tr><td>%s</td></tr>' % s_table.replace('"','"') + s += '<tr><td>' + resstr.replace('resulttable','resulttabletext').replace('"','quot;') + '</td></tr>' + s += '<tr><td><hr color="red" size=1></td></tr><tr><td><font color="#777777"><font size="-1"><i><font size="-2">Created by AtlRunQuery on: %s</font></i></font></td></tr></table><script type="text/javascript"></script></body></html>' % str(datetime.datetime.now()) + s += "')" + s += '" title="Query results in text format for easy printing">Print version of results</a>' + s += '</td></tr>' + return s + + + @classmethod + def _XMLFormat(cls, datapath, xmlfilename, xmlhtmlstr): + if xmlhtmlstr!=None: + s = '''<tr><td style="vertical-align: top"><img vspace=0 src="images/xml-small.gif"> </td><td> + <a href="./%s/%s" target="_blank" title="Query result as LB collection in standard XML good-run-list format">Query result as standard <b>Good Run-LB List in XML format</b> (right-click link to download)</a> / + <a href="./LumiRangeCollection.dtd" target="_blank" title="Corresponding DTD file">DTD file</a> + </td></tr>''' % (datapath, xmlfilename) + s += '<tr><td style="vertical-align: top"></td><td>' + s += '<a href="javascript:openTextWindow(' + s += "'Print','<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html xmlns:"my"><head><title>Run query result</title><LINK href="atlas-runquery.css" rel="stylesheet" type="text/css"><body><table style="padding: 10px;font-family: sans-serif; font-size: 95%%">" + s += '<tr><td>%s</td></tr>' % xmlhtmlstr + s += '</table></body></html>' + s += "')" + s += '" title="Query result in text format">Query result as standard <b>Good Run-LB List in pretty-text format</b> for visual inspection (click for pop-up)</a>' + s += '</td></tr>' + return s + else: + return '''<tr><td style="vertical-align: top;"><img vspace=0 src="images/xml-small.gif"> </td><td style="color: #000064;"> + Not available. <font color="#333333">The creation of <b>GRLs</b> based on the query result has been disabled by default. Please use option <b><i>grl</i></b> as shown in the examples under <font style="color:red">Xtras</font></font></td></tr>''' + + diff --git a/Database/CoolRunQuery/python/html/AtlRunQueryHtmlUtils.py b/Database/CoolRunQuery/python/html/AtlRunQueryHtmlUtils.py new file mode 100755 index 00000000000..1debfac41c0 --- /dev/null +++ b/Database/CoolRunQuery/python/html/AtlRunQueryHtmlUtils.py @@ -0,0 +1,435 @@ +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + +import time, datetime + +from CoolRunQuery.AtlRunQueryRun import Run +from CoolRunQuery.AtlRunQueryQueryConfig import QC + +def WrapIntoHTML(content, title="Run query result", extracss=None): + wrap = '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' + wrap += '<html xmlns:"my"><head><title>%s</title>' % title + wrap += '<LINK href="atlas-runquery-lb.css" rel="stylesheet" type="text/css">' + if extracss: + for css in extracss: + wrap += '<LINK href="%s" rel="stylesheet" type="text/css">' % css + wrap += '</head>' + wrap += '<body>%s</body></html>' % content + return wrap + + +def OpenWindow(content, title="Run query result", extracss=None, size="normal"): + wrap = WrapIntoHTML(content, title, extracss) + openWindowCmd = 'openWindow' + if size=='large': openWindowCmd = 'openLargeWindow' + return "<a href=\"javascript:%s('Print', '%s')\">" % ( openWindowCmd, wrap.replace('"','"') ) + + +def CreatePopupHtmlPage(name,wincontent): + filename = 'popupContent_%s.html' % name + outfn = '%s/%s' % (Run.Datapath, filename) + fh = open(outfn,"w") + if type(wincontent)==list: + for l in wincontent: + print >> fh, l + else: + print >> fh, wincontent + fh.close() + return outfn + + + +def CreateLBTable(run): + body = '<table class="outer">' + body += '<tr><td><b>LB start times (%s) and durations for run %i:</b><br>' % (QC.tzdesc(),run.runNr) + body += '<font size="-1" color="#888888">(Begin/end of run: %s)</font>' % run.timestr('html', run.runNr != Run.runnropen) + body += '<hr color="black" size=1>' + body += '<table class="lb">' + body += '<tr><th>LB</th><th>Start time (%s)</th><th>Duration (sec)</th></tr>' % QC.tzdesc() + body += '<tr>' + for idx,(lbtime,lbendtime) in enumerate(run.lbtimes): + lb=idx+1 + timetuple = time.localtime(lbtime/1.E9) if QC.localtime else time.gmtime(lbtime/1.E9) + body += '<tr><td>%s</td><td>%s</td><td>%.2f</td></tr>' % (lb, time.strftime('%X',timetuple), (float(lbendtime)-float(lbtime))/1.E9) + if run.runNr == Run.runnropen: # run still ongoing + body += '<tr><td style="text-align:left"colspan="3"><i> Run still ongoing ...</i></td></tr>' + body += '</tr></table>' + body += '<hr color="red" size=1><font color="#777777"><font size="-1"><i><font size="-2">Created by AtlRunQuery on: %s</font></i></font></td></tr></table>' % str(datetime.datetime.now()) + body += '</td></tr></table>' + return body.replace('"','"') + + + +def CreateLBTooltip(run): + if len(run.lbtimes)==0: return + + content = '<strong><b>LB start times (%s) and durations for run %i:</b></strong><br>Begin/end of run: %s' % (QC.tzdesc(), run.runNr, run.timestr('html', run.runNr != Run.runnropen)) + content += '<hr style="width:100%; background-color: #BBBBBB; height:1px; margin-left:0; border:0"/>' + content += '<table style="width: auto; border: 0px solid; border-width: margin: 0 0 0 0; 0px; border-spacing: 0px; border-collapse: separate; padding: 0px; font-size: 90%">' + content += '<tr>' + if len(run.lbtimes) > 150: + content += '<td><i>Too many LBs to show ... (click instead!)</i></td>' + else: + for idx,(lbstart, lbend) in enumerate(run.lbtimes): + lb=idx+1 + timetuple = time.localtime(lbstart/1.E9) if QC.localtime else time.gmtime(lbstart/1.E9) + content += '<td style="text-align:right"> %s: %s </td><td style="text-align:right">(%.1f sec)</td>' % (lb, time.strftime("%X",timetuple), (float(lbend)-float(lbstart))/1.E9) + if lb%6==0: + content += '</tr><tr>' + content += '</tr></table>' + content += '<hr style="width:100%; background-color: #BBBBBB; height:1px; margin-left:0; border:0"/>' + content += '<font color="#AA0000">Click to obtain full list in independent window!</font>' + run.addToolTip( 'LBStart_%i' % run.runNr, content ) + + + +def CreateDQTooltip(run): + if not 'DQ' in Run.ShowOrder: return + + def_with_primaries = run.stats['DQ']["primaries"] + condensed = True + if not condensed: + for x in sorted(def_with_primaries.keys()): + l_ready = [y for y in def_with_primaries[x] if run.data.isReady( (y.since,y.until) )] + if len(l_ready)==0: continue + content = '<strong><b>%s: comments for run %i:</b></strong><br><span style="font-size: 80%%">ATLAS READY in LBs %s</span>' % (x,run.runNr,', '.join(["%i−%i" % (r.startlb, r.endlb-1) for r in run.data.isReady()])) + content += '<hr width="100%%">' + content += '<table style="width: auto; white-space: nowrap; border: 0px solid; margin: 0 0 0 0; border-spacing: 0px; border-collapse: separate; padding: 0px;">' + for y in l_ready: + s = "%i−%i" % (y.since,y.until-1) if y.until-y.since!=1 else "%i" % (y.since) + content += '<tr><td style="color:blue; padding-right: 5px;">LB %s</td>' % s # the LB info + content += '<td style="font-weight:bold; padding-right: 5px;">%s</td><td><i>%s</i></td></tr>' % (y.description.split("->")[-1], y.comment) # the primary and the comment + content += '</table>' + run.addToolTip("dqdefect_%i_%s" % (run.runNr,x), content) + else: + from itertools import groupby + from operator import attrgetter + gr = {} + for k in def_with_primaries: + d = {} + for p,v in groupby(sorted(def_with_primaries[k]), key=attrgetter('description', 'comment')): + d[p] = [(e.since, e.until) for e in v] + gr[k] = d + + for x in sorted(gr): + content = '<strong><b>%s: comments for run %i:</b></strong><br><span style="font-size: 80%%">ATLAS READY in LBs %s</span>' % (x,run.runNr,', '.join(["%i−%i" % (r.startlb, r.endlb-1) for r in run.data.isReady()])) + content += '<hr width="100%%">' + content += '<table style="width: auto; white-space: nowrap; border: 0px solid; margin: 0 0 0 0; border-spacing: 0px; border-collapse: separate; padding: 0px;">' + for pdef in sorted(gr[x]): + l_ready = [y for y in gr[x][pdef] if run.data.isReady( y )] + if len(l_ready)==0: continue + s = ", ".join(["%i−%i" % y if y[1]-y[0]!=1 else "%i" % (y[0]) for y in l_ready]) + content += '<tr><td colspan="2" style="color:blue; padding-right: 5px; max-width: 130;">LB %s</td></tr>' % s # the LB info + content += '<tr><td style="font-weight:bold; padding-right: 5px;">%s</td><td><i>%s</i></td></tr>' % (pdef[0].split("->")[-1], pdef[1]) # the primary and the comment + content += '</table>' + run.addToolTip("dqdefect_%i_%s" % (run.runNr,x), content) + + +def CreateStreamOverlapTooltip(run,k): + if not run.stats[k.ResultKey]['StrOverlap']: + ovstr = '<tr><td width="200"><b><i>None (or not available for this run)</i></b></td></tr>' + else: + ovstr = '' + nacc=0 + for stream, fraction in run.stats[k.ResultKey]['StrOverlap']: + nacc+=1 + tdstr = '<td class="tdov%i">' % (nacc%2+1) + fs = "%.2g" % fraction + if fraction==100: fs = "100" + ovstr += '<tr>%s%s</td>%s = </td>%s%s%%</td></tr>' % (tdstr, stream, tdstr, tdstr, fs) + strpairs = [('STR:physics_MuonswBeam', 'physics_L1Calo'), + ('STR:physics_MinBias', 'physics_L1Calo'), + ('STR:physics_Muons', 'physics_Egamma'), + ('STR:physics_Muons', 'physics_JetTauEtmiss'), + ('STR:physics_Egamma', 'physics_JetTauEtmiss'), + ('STR:physics_MinBias', 'physics_JetTauEtmiss')] + for st in strpairs: + if k==st[0] and stream==st[1]: + s1 = st[0].replace('STR:','') + fname = 'data_' + s1.strip() + '_' + st[1].strip() + '.txt' + f = open(Run.Datapath + '/' + fname,'a') + f.write('%i %f\n' % (run.runNr,fraction)) + f.close() + break + + boxcontent = '<table class="streamtiptable"><tr><td>' + boxcontent += '<strong><b>Info for stream: <font color="#AA0000">%s</font></b></strong>' % k.Header[4:] + boxcontent += '</td></tr><tr><td>' + boxcontent += '<hr style="width:100%; background-color: #999999; height:1px; margin-left:0; border:0"/>' + boxcontent += '</td></tr>' + boxcontent += '<tr><td>' + boxcontent += '<strong><b>Stream overlaps</b></strong> (nonzero overlaps only):' + boxcontent += '</td></tr><tr><td>' + boxcontent += '<table class="overlaptable">%s</table><strong>' % ovstr + boxcontent += '</td></tr>' + + # Tier-0 output + prodsteps = ['StrTier0TypesRAW', 'StrTier0TypesESD' ] + allt0text = '' + for p in prodsteps: + if run.stats[k.ResultKey].has_key(p): + typelist = run.stats[k.ResultKey][p].keys() + if not typelist: continue + + typelist.sort() + t0text = '' + for i, t0out in enumerate(typelist): + if not 'TMP' in t0out: # don't show temporary output + if i%4==0 and i>0: t0text += '<br>' + if t0out == 'NTUP': t0outtxt = 'NTUP_... <font size="-2">(types as above)</font>' # stands for all NTUP types in merge step + else: t0outtxt = t0out + if run.stats[k.ResultKey][p][t0out]: # output on CAF + t0text += '<font color="#BB0000">%s</font>, ' % t0outtxt + else: + t0text += '%s, ' % t0outtxt + + if t0text != '': + allt0text += '<strong><b><i>' + if 'RAW' in p: + allt0text += 'Produced by reconstruction step' + if run.stats[k.ResultKey].has_key('StrTier0AMI') and run.stats[k.ResultKey]['StrTier0AMI']: + if run.stats[k.ResultKey]['StrTier0AMI'].has_key(p): + allt0text += ' (AMI tag: %s)' % run.stats[k.ResultKey]['StrTier0AMI'][p] + else: + allt0text += ' (AMI tag: %s)' % 'UNKNOWN' + else: + allt0text += 'Produced by merge step' + + allt0text += ':</i></b></strong>' + + allt0text += '<table class="overlaptable"><tr><td style="padding-left:10px">' + t0text[:len(t0text)-2] + '</td></tr></table>' + if p != prodsteps[-1]: allt0text += '' + + if allt0text != '': + boxcontent += '<tr><td>' + boxcontent += '<hr style="width:100%; background-color:#999999; height:1px; margin-left:0; border:0"/>' + boxcontent += '</td></tr><tr><td>' + boxcontent += '<strong><b>Tier-0 output types for this stream:</b></strong><br>(Datasets in <font color="#BB000"><strong><b>red</b></strong></font> are <font color="#BB000">replicated to CAF</font>' + boxcontent += '</td></tr><tr><td>' + boxcontent += '<table class="overlaptable" style="color:#222222"><tr><td>%s</td></tr></table>' % allt0text + boxcontent += '</td></tr>' + + # events-per-LB information + lbrecinfo = run.stats[k.ResultKey]['LBRecInfo'] + if not lbrecinfo: + lbrecinfo = '<tr><td style="font-size:75%"><i>No LB information in SFO DB. <br> Probably because files for this stream <br>were not closed at LB boundaries by SFO.</i></td></tr>' + else: + ev = dict(lbrecinfo) + for lb in ev: ev[lb]=0 + for lb,nev in lbrecinfo: ev[lb] += nev + lbs = ev.keys(); lbs.sort() + output = '<tr>' + idx = 0 + while idx <len(lbs): + output += '<td style="font-size:75%%;text-align:right">%i (%s)</td>' % (lbs[idx],ev[lbs[idx]]) + if len(lbs)>50 and idx==23: + output += '</tr><tr><td style="font-size:75%; text-align:left" colspan="8"> ... <i>too many LBs ... show begin and end of run only...</i></td></tr>' + idx = len(lbs) - 23 + elif (idx+1)%8==0: output += '</tr><tr>' # newline every 10 + idx += 1 + output += '</tr>' + lbrecinfo = output + + boxcontent += '<tr><td>' + boxcontent += '<hr style="width:100%; background-color:#999999; height:1px; margin-left:0; border:0"/>' + boxcontent += '<strong><b>Recorded LB (#Events)</b></strong></strong>:' + boxcontent += '</td></tr><tr><td>' + boxcontent += '<table class="eventsperlbstreamtable" style="color:#222222">%s</table>' % lbrecinfo + boxcontent += '</td></tr></table>' + if not 'No LB information' in lbrecinfo: + boxcontent += '<font color="#AA0000"><strong><b>Click to obtain the full LB list and plots in independent window!</b></strong></font>' + + run.addToolTip("STROV_%i_%s" % (run.runNr, k.ResultKey[4:]), boxcontent) + + + + + +def createRatePopupWindow(v,run): + from CoolRunQuery.output.AtlRunQueryRoot import makeRatePlot + histoText = '' # upper left on above the histogramm + + lbduration = [(idx+1,lbtime,(lbendtime-lbtime)/1e9) for idx,(lbtime,lbendtime) in enumerate(run.lbtimes) ] + + duration = (run.lbtimes[-1][1] - run.lbtimes[0][0])/1e9 + averrate = [] + for tr in v: + averrate.append((tr, sum([int(co[3]) for co in v[tr]])/duration)) + averrate.sort(lambda x,y: cmp(y[1],x[1])) + + paths = [] + wincmds = [] + triggergroups = [] + loopcntr = 0 + plotstart = 0 + plotrange = 8 + triggers_in_range = averrate[plotstart:plotstart+plotrange] + while len(triggers_in_range)>0: + path = makeRatePlot( v, lbduration, triggers_in_range, averrate, 'Luminosity block number', 'Rate [Hz]', + 'trigcounts%i_vs_lb_run_%i' % (loopcntr,run.runNr), + 'Trigger Rates for run %i' % (run.runNr), + Run.Datapath, histoText ) + paths += [path] + + wincmd = createRateWinContent(loopcntr, v, lbduration, triggers_in_range, run, path) + wincmds += [wincmd] + + triggergroups += [triggers_in_range] + + loopcntr += 1 + plotstart += plotrange + triggers_in_range = averrate[plotstart:plotstart+plotrange] + + return triggergroups,paths,wincmds + + + + +def createRateWinContent(loopcntr, v, lbduration, triggers_in_range, run, path): + + triggers = [tr[0] for tr in triggers_in_range] + + # create window and tooltip + wincontent = ['<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">'] + wincontent += ['<html xmlns:"my">'] + wincontent += [' <head>'] + wincontent += [' <title>Run query result for trigger rates</title>'] + wincontent += [' <LINK href="atlas-runquery-lb.css" rel="stylesheet" type="text/css">'] + wincontent += [' </head>'] + wincontent += [' <body>'] + wincontent += [' <table class="outer" style="padding: 5px">'] + wincontent += [' <tr><td><strong><b>Trigger rates for run %s:</b></strong><br><font size="-1" color="#888888">(Begin/end of run: %s)</font></td></tr>' % (run.runNr, run.timestr('html', run.runNr != Run.runnropen))] + wincontent += [' <tr><td colspan="2"><hr color="black" size=1></td></tr>'] + wincontent += [' <tr><td><img src="%s" align="left"></td>' % path.rsplit('/')[-1]] + wincontent += [' <td><img src="%s" align="left"></td></tr>' % path.rsplit('/')[-1].replace('.png','_log.png')] + wincontent += [' <tr><td colspan="2"><hr color="black" size=1></td></tr>'] + wincontent += [' <tr><td colspan="2" style="font-size:70%; height:30px; text-align: right; vertical-align: middle;"><b>TBP</b> - <i>trigger before prescale</i>,<b>TAV</b> - <i>trigger after veto</i>, <b>DTF</b> - <i>dead time fraction</i></td></tr>'] + wincontent += [' <tr><td colspan="2">'] + wincontent += [' <table class="LB">'] + header = '<th>LB</th><th>Start time</th><th>Duration</th>' + header2 = '<th></th><th>(%s)</th><th>(s)</th>' % QC.tzdesc() + for tr in triggers: + if "UNPAIRED" in tr or "EMPTY" in tr or "FILLED" in tr: + header += '<th colspan="3">%s<BR>_%s</th>' % tuple(tr.rsplit('_',1)) + else: + header += '<th colspan="3">%s</th>' % tr + header2 += '<th>TBP</th><th>TAV</th><th>DTF</th>' + wincontent += [' <thead>'] + wincontent += [' <tr>%s</tr>' % header] + wincontent += [' <tr class="second">%s</tr>' % header2] + wincontent += [' </thead>'] + wincontent += [' <tbody>'] + for lb, lbstart, dt in lbduration: + timetuple = time.localtime(lbstart/1.E9) if QC.localtime else time.gmtime(lbstart/1.E9) + linecontent = '<td>%i</td><td>%s</td><td class="dt">%3.1f</td>' % (lb,time.strftime("%X",timetuple),dt) + for tr in triggers: + _lb,bp,ap,av = v[tr][lb-1] + try: avr=float(av)/dt + except: avr=0 + try: bpr=float(bp)/dt + except: bpr=0 + try: apr=float(ap)/dt + except: apr=0 + deadtime=0 + bg = "" + if apr>0: + deadtime = ( apr - avr ) / apr + if deadtime>0.90: bg=' style="background-color: #ff0000;"' + elif deadtime>0.30: bg=' style="background-color: #ff3333;"' + elif deadtime>0.10: bg=' style="background-color: #ff6666;"' + elif deadtime>0.05: bg=' style="background-color: #ff9999;"' + elif deadtime>0.01: bg=' style="background-color: #ffcccc;"' + linecontent += '<td>%3.1f</td><td>%3.1f</td><td%s class="dt">(%2.1f%%)</td>' % (bpr,avr,bg,100*deadtime) + if lb%2!=0: col = '#eeeeee' + else: col = '#ffffff' + wincontent += [' <tr style="background:%s">%s</tr>' % (col,linecontent)] + + if run.runNr == Run.runnropen: # run still ongoing + wincontent += [' <tr><td style="text-align:left"colspan="%i"><i> Run still ongoing ...</i></td></tr>' % (len(triggers)+1)] + wincontent += [' </tbody>'] + wincontent += [' </table>'] + wincontent += [' </td></tr>'] + wincontent += [' <tr><td colspan="2"><hr color="red" size=1></td></tr>'] + wincontent += [' <tr><td colspan="2">'] + wincontent += [' <font color="#777777" size="-1"><i><font size="-2">Created by AtlRunQuery on: %s</font></i></font>' % str(datetime.datetime.now())] + wincontent += [' </td></tr>'] + wincontent += [' </table>'] + wincontent += [' </body>'] + wincontent += ['</html>'] + + CreatePopupHtmlPage('trRates_%i_%i' % (run.runNr, loopcntr), wincontent) + return '' + + + + + +def makeSummaryPlotForLHC(run): + from CoolRunQuery.output.AtlRunQueryRoot import makeLBPlotSummaryForLHC + # make summary of all LHC information + # requires: beam intensities, beam energy, stable beams + # keys = [ 'olc:beam1intensity', 'olc:beam2intensity', 'lhc:beamenergy', 'olc:lumi:0' ] + xvec = [] + yvec = [[],[],[]] # note that this is not the same as 3*[[]] + ymax = 3*[-1] + + # stable beams + hasStableBeamsInfo, xvecStb = run.getStableBeamsVector() + for ik,k in enumerate( [ 'olc:beam1intensity', 'olc:beam2intensity', 'lhc:beamenergy' ] ): + if not k in Run.ShowOrder: continue + for entry in run.data[k]: + if entry.startlb == 0: continue + + val = float(entry.value) if (entry.value!='n.a.' and entry.value>0) else 0 + + if ik==0 or ik==1: + if val > 1e30: val = 0 + val *= 1.0 # beam intensities in 10^11 protons + elif ik==2: + if val >= 7864: val = -1 + + lastlb = min(entry.lastlb,run.lastlb) + + if ik==0: + xvec += range(entry.startlb,lastlb+1) + + yvec[ik] += (lastlb-entry.startlb+1) * [val] + + for ilb in xrange(entry.startlb,lastlb+1): + if val > ymax[ik]: + if not hasStableBeamsInfo or ilb in xvecStb: ymax[ik] = val # find max + + histoText = '' + path = makeLBPlotSummaryForLHC( xvec, xvecStb, yvec, run.runNr, Run.Datapath, histoText ) + + return path, xvec, yvec, ymax + + +def makeSummaryPageForLHC(run, yvec, path): + # create window and tooltip + wincontent = '<table class="outer" style="padding: 5px; font-family: sans-serif; font-size: 85%"><tr><td>' + wincontent += '<strong><b>LHC beam energy and intensities during run %i:</b></strong><br><font size="-1"><font color="#888888">(Begin/end of run: %s)</font></font><hr color="black" size=1>' % (run.runNr, run.timestr('html', run.runNr != Run.runnropen)) + wincontent += '<table style="padding: 0px"><tr><td>' + wincontent += '<img src="%s" align="left"></td>' % path + wincontent += '</td></tr></table>' + wincontent += '<hr color="black" size=1>' + wincontent += '<table class="lb">' + wincontent += '<tr><th style="text-align:right">LB</th><th style="text-align:right"> Start time<br> (%s)</th><th style="text-align:right"> Duration<br>(sec)</th><th style="text-align:right"> Beam energy<br>(GeV)</th><th style="text-align:right"> Intensity Beam-1<br>(1e11 protons)</th><th style="text-align:right"> Intensity Beam-2<br>(1e11 protons)</th>' % QC.tzdesc() + wincontent += '</tr><tr style="background:%s">' % '#eeeeee' + nbeam1intensity = len(yvec[0]) + nbeam2intensity = len(yvec[1]) + nbeamenergy = len(yvec[2]) + for idx,(lbtime,lbendtime) in enumerate(run.lbtimes): + lb=idx+1 + timetuple = time.localtime(lbtime/1.E9) if QC.localtime else time.gmtime(lbtime/1.E9) + beam1intensity = yvec[0][idx] if idx<nbeam1intensity else 0 + beam2intensity = yvec[1][idx] if idx<nbeam2intensity else 0 + beamenergy = yvec[2][idx] if idx<nbeamenergy else 0 + wincontent += '<td>%i</td><td>%s</td><td>%.2f</td><td>%i</td><td>%.4g</td><td>%.4g</td>' % (lb, time.strftime("%X",timetuple), (float(lbendtime)-float(lbtime))/1.E9, beamenergy, beam1intensity, beam2intensity) + if idx%2!=0: col = '#eeeeee' + else: col = '#ffffff' + wincontent += '</tr><tr style="background:%s">' % col + if run.runNr == Run.runnropen: # run still ongoing + wincontent += '<tr><td style="text-align:left"colspan="4"><i> Run still ongoing ...</i></td></tr>' + wincontent += '</tr></table>' + wincontent += """<hr color="red" size=1><font color="#777777"><font size="-1"><i><font size="-2">Created by AtlRunQuery on: %s</font></i></font></td></tr></table>""" % str(datetime.datetime.now()) + return wincontent diff --git a/Database/CoolRunQuery/python/html/AtlRunQueryPageMaker.py b/Database/CoolRunQuery/python/html/AtlRunQueryPageMaker.py new file mode 100644 index 00000000000..7421e7ed9ad --- /dev/null +++ b/Database/CoolRunQuery/python/html/AtlRunQueryPageMaker.py @@ -0,0 +1,50 @@ +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + + +import os, re + +class PageMaker: + + @classmethod + def makePage(cls, body, origQuery=None, extraReplace=None, removeExamples=False): + from CoolRunQuery.AtlRunQueryQueryConfig import QC + + (top, bottom) = cls.getBlankPage(removeExamples=removeExamples) + # put in the original query + if origQuery != None: + top = top.replace('name="q"','name="q" value="%s"' % origQuery) + + # extra replacements + if extraReplace != None: + for pat,repl in extraReplace: + p = re.compile(pat,re.S) + top = p.sub(repl, top) + bottom = p.sub(repl, bottom) + + fh = open("%s/index.html" % QC.datapath, "w") + print >> fh, top + print >> fh, body.encode("utf-8") + print >> fh, bottom + fh.close() + + cls._copySomeFilesToCache() + + + @classmethod + def getBlankPage(cls, removeExamples=False): + if '/var/vhost' in os.path.dirname(__file__): + page = open("%s/index.html" % os.path.dirname(__file__).replace('CoolRunQuery/html','')) + else: + page = open("%s/atlas-runquery.html" % os.path.dirname(__file__).replace('python/CoolRunQuery/html','html/CoolRunQuery')) + s = page.read() + top, bottom = re.match("(.*)<!--INSERTQUERYRESULT-->(.*)", s, re.S|re.M).group(1,2) + if removeExamples: + top = "".join(re.match("(.*)<!--START_EXAMPLES-->.*<!--END_EXAMPLES-->(.*)", top, re.S|re.M).group(1,2)) + return (top,bottom) + + + @classmethod + def _copySomeFilesToCache(cls): + from commands import getoutput + from CoolRunQuery.AtlRunQueryQueryConfig import QC + getoutput("cp html/atlas-runquery-lb.css %s" % QC.datapath) diff --git a/Database/CoolRunQuery/python/html/AtlRunQuerySummary.py b/Database/CoolRunQuery/python/html/AtlRunQuerySummary.py new file mode 100644 index 00000000000..58e38a4e81a --- /dev/null +++ b/Database/CoolRunQuery/python/html/AtlRunQuerySummary.py @@ -0,0 +1,651 @@ +#!/bin/env python + +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration +# +# ---------------------------------------------------------------- +# Script : AtlRunQuerySummary.py +# Project: AtlRunQuery +# Purpose: Library with the Summary maker +# Authors: Andreas Hoecker (CERN), Joerg Stelzer (DESY) +# Created: Oct 6, 2009 +# ---------------------------------------------------------------- +# +import sys, time +from array import array +from CoolRunQuery.utils.AtlRunQueryUtils import prettyNumber, durationInSeconds, filesize, importroot +from CoolRunQuery.utils.AtlRunQueryLookup import DQChannelDict, DQSuperGroupsDict, DQGroupDict, DQChannels +from CoolRunQuery.output.AtlRunQueryRoot import SetStyle, MakeHtml +from CoolRunQuery.selector.AtlRunQuerySelectorBase import DataKey +importroot() +from ROOT import TCanvas, gPad, gStyle, TGraph, TColor, TGaxis, gROOT, TH1F, TLegend, TText, TLine, Double + +# custom ROOT colours +c_White = TColor.GetColor( "#ffffff" ) +c_NovelBlue = TColor.GetColor( "#2244a5" ) +c_DarkOrange = TColor.GetColor( "#ff6600" ) +c_NovelRed = TColor.GetColor( "#dd0033" ) +c_DarkGreen = TColor.GetColor( "#116600" ) +c_Violet = TColor.GetColor( "#aa11aa" ) +c_LightBlue = TColor.GetColor( "#66aaff" ) +c_VDarkYellow = TColor.GetColor( "#ffa500" ) +c_Gray = TColor.GetColor( "#888888" ) +c_VLightGray = TColor.GetColor( "#eeeeee" ) +c_VDarkGray = TColor.GetColor( "#333333" ) + +def MakeSummaryTableHtml( cornerstr, colstr, rowkeys, table, tablewidth ): + + # dimensions + ncol = len(colstr) + nrow = len(rowkeys) + + s = '<table class="resulttable" width="%i" style="margin-left: 14px">\n' % tablewidth + # headline + s += '<tr>\n' + if cornerstr != '': s += ' <th style="text-align:left">%s</th> ' % cornerstr + else: s += ' <th class="emptycorner">−</th>' + for ic in range(0,ncol): + s += '<th>%s</th>' % colstr[ic] + s += '</tr>\n' + for ir in range(0,nrow): + if ir%2 == 0: color = 1 + else: color = 2 + if 'debug_' in rowkeys[ir][1]: s += '<tr class="outRed%s">\n' % color # special colours for debug stream + else: s += '<tr class="out%s">\n' % color + s += ' <th class="rowsum" style="text-align:left">%s </th> ' % rowkeys[ir][1] + for ic in range(ncol): + if ic in rowkeys[ir][4]: # exclude from printing + s += '<td style="text-align:center">−</td' + continue + s += '<td style="text-align:right">' + if isinstance(table[ir][ic],str): + s += "%s" % table[ir][ic] # example filesize + elif rowkeys[ir][3] == 'pretty-int': + s += "%s" % prettyNumber(int(table[ir][ic])) # example '#Event' + else: + s += (rowkeys[ir][3] % table[ir][ic]) # format + s += '%s</td>' % rowkeys[ir][2] # unit + + s += '</tr>\n' + s += '</table>' + + return s + +def MakeDQSummaryTableHtml( cornerstr, title, colstr, colchans, rowstr, rowstrNames, dic, nruns, labeloffset ): + + if nruns <= 0: return '' + + # dimensions + ncol = len(colstr) # flag types + nrow = len(rowstr) # DQ channels + + # overlay pictures + dw_call = '<script type="text/javascript">\n' + dw_call += """if(dw_Tooltip.content_vars==undefined) {dw_Tooltip.content_vars = {}; };\n""" + for ir in range(0,nrow): + for ic in range(0,ncol): + if dic[colchans[ic]][rowstr[ir]][0] > 0: + var = 'var_%i_%i' % (ir,ic+labeloffset) + dw_call += 'dw_Tooltip.content_vars["%s"] = {' % var + dw_call += "content: '<strong><b>Corresponding run(s) [# = %i]:</b></strong><br> " % dic[colchans[ic]][rowstr[ir]][0] + icc = 0 + for r in dic[colchans[ic]][rowstr[ir]][1]: + dw_call += '%s, ' % r + icc += 1 + if icc%8 == 0: dw_call += '<br>' + if dw_call[-1-3:] == '<br>': dw_call = dw_call[0:-1-3] # remove ', <br>' + else: dw_call = dw_call[0:-1-1] # remove ', ' + dw_call += """ ' };\n""" + dw_call += '</script>\n\n' + + s = '<table class="resulttable" width="auto" style="margin-left: 14px">\n' + # headline + s += '<tr>\n' + if cornerstr != '': s += ' <th class="rowsum" style="text-align:left" rowspan="2">%s</th> ' % cornerstr + else: s += ' <th class="emptycorner" rowspan="2">−</th>' + s += '<th class="colsumL" colspan="%i">%s</th></tr><tr>' % (ncol, title) + for ic in range(0,ncol): + s += '<th class="colsum">%s</th>' % colstr[ic] + s += '</tr>\n' + for ir in range(0,nrow): + if ir%2 == 0: color = 1 + else: color = 2 + s += '<tr class="out%s">\n' % color + s += ' <th class="rowsum" style="text-align:left">%s</th> ' % rowstrNames[ir] + for ic in range(0,ncol): + dqk = rowstr[ir] + dqk_tdtype = dqk + if dqk == 'n.a.': dqk_tdtype = 'NA' + s += '<td class="td%s" style="text-align:center">' % dqk_tdtype + if dic[colchans[ic]][rowstr[ir]][0] > 0: s += '<div class="showTip var_%i_%i" style="display:inline;cursor:pointer">' % (ir,ic+labeloffset) + if dic[colchans[ic]][dqk][0]>0: s += '%i' % (dic[colchans[ic]][dqk][0]) + if dic[colchans[ic]][rowstr[ir]][0] > 0: s += '</div>' + s += '</td>' + + s += '</tr>\n' + s += '</table>\n\n' + s += dw_call + s += '\n' + + return s + +def ComputeStats( vlist ): + if len(vlist) == 0: return [0,0,0,0,0] + tot = 0 + mn = +sys.maxint + mx = -sys.maxint + for v in vlist: + tot += v + mn = min(mn,v) + mx = max(mx,v) + mean = float(tot)/len(vlist) + return [tot,mean,mn,mx,len(vlist)] + +def DecodeStr( s, tpe ): + if 'Duration' == tpe: + return durationInSeconds(s)/float(3600) + elif 'STR:physics_*' == tpe: + s = s[0].value + if len(s) <= 1: + print 'Big troubles... in function "AtlRunQuerySummary::DecodeStr": invalid length for "%s"' % tpe + sys.exit(1) + if s[0] <= 0: return 0 + else: return float(s[1])/s[0]/1.0e6 + + # otherwise + return int(s[0].value) + +def DrawGraph( graph, col, width, style, legend, title, + drawNumber=False, unit='M', text=TText() ): + hist = graph.GetHistogram() + hist.SetLineColor( col ) + graph.SetLineColor( col ) + hist.SetLineWidth( width ) + graph.SetLineWidth( width ) + hist.SetLineStyle( style ) + graph.SetLineStyle( style ) + graph.Draw("lsame") + legend.AddEntry( hist, title, "L" ) + + # draw max number + if drawNumber: + N = graph.GetN() + minunit_ = Double(0) + maxunit_ = Double(0) + minnev_ = Double(0) + maxnev_ = Double(0) + x = Double(0) + y = Double(0) + graph.GetPoint( 0, minunit_, minnev_ ) + graph.GetPoint( N-1, maxunit_, maxnev_ ) + dist = maxunit_ - minunit_ + graph.GetPoint( N-1,x,y ) + + text.SetTextAlign( 12 ) + text.SetTextSize( 0.030 ) + text.SetTextColor( c_NovelBlue ) + if y >= 100: yt = '%.0f' % y + else: yt = '%#.3g' % y + text.DrawText( x+0.015*dist, y, '%s %s' % (yt, unit) ) + +def SetFrameStyle( frame, scale = 1.0 ): + + c_VLightGray = TColor.GetColor( "#eeeeee" ) + c_VDarkGray = TColor.GetColor( "#333333" ) + + frame.SetLabelOffset( 0.012, "X" ) + frame.SetLabelOffset( 0.012, "Y" ) + frame.GetXaxis().SetTitleOffset( 1.25 ) + frame.GetYaxis().SetTitleOffset( 0.94 ) + frame.GetXaxis().SetTitleSize( 0.045*scale ) + frame.GetYaxis().SetTitleSize( 0.045*scale ) + labelSize = 0.04*scale + frame.GetXaxis().SetLabelSize( labelSize ) + frame.GetYaxis().SetLabelSize( labelSize ) + gPad.SetTicks() + gPad.SetBottomMargin( 0.120*scale ) + gStyle.SetTitleFillColor( c_VLightGray ) + gStyle.SetTitleTextColor( c_VDarkGray ) + gStyle.SetTitleBorderSize( 1 ) + gStyle.SetTitleH( 0.06 ) + gStyle.SetTitleX( gPad.GetLeftMargin() ) + gStyle.SetTitleY( 1 - gPad.GetTopMargin() + gStyle.GetTitleH() ) + gStyle.SetTitleW( 1 - gPad.GetLeftMargin() - gPad.GetRightMargin() ) + +def CreateCanvasAndFrame( runmin, runmax, tminS, tmaxS, maxnev, type, unit ): + # plots the graphs + title = "ATLAS integrated numnber of events in run range %i - %i" % (runmin, runmax) + c = TCanvas( "c", title, 0, 0, 800, 550 ) + c.GetPad(0).SetRightMargin(0.08) + c.GetPad(0).SetLeftMargin(0.10) + c.GetPad(0).SetTopMargin(0.07) + htitle = "Events recorded by ATLAS between %s and %s" % (time.strftime('%b %d',tminS), + time.strftime('%b %d, %Y',tmaxS)) + + frame = TH1F( "frame", htitle.strip(), 10000, runmin, runmax ) + + SetFrameStyle( frame ) + TGaxis.SetMaxDigits(6) + frame.GetYaxis().SetTitleOffset( 1.0 ) + frame.SetMinimum( 0 ) + frame.SetMaximum( maxnev*1.1 ) + frame.SetYTitle( "Number of events (in %s)" % unit ) + frame.SetXTitle( "Run number" ) + frame.Draw() + + # legend for plot + dx = 0.04 + dy = -0.05 + legend = TLegend( c.GetLeftMargin() + dx, 1 - c.GetTopMargin() + dy, + c.GetLeftMargin() + 0.48 + dx, 1 - c.GetTopMargin() - 0.38 + dy) + legend.SetFillStyle( 1001 ) + legend.SetFillColor( c_VLightGray ) + legend.SetBorderSize(1) + legend.SetMargin( 0.2 ) + legend.SetTextColor( c_VDarkGray ) + legend.Draw("same") + + return c, frame, legend + +def MakeRootPlots( rootstreamdic, datapath ): + + # suppress output + gROOT.SetBatch( 1 ) + + # common style, and a few corrections + SetStyle() + + colours = [ c_NovelRed, 4, c_DarkOrange, c_DarkGreen, c_Violet, c_LightBlue, 3, c_VDarkYellow, c_Gray, + 1, 2, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28 ] + + # run ranges (note: reverse order!) + run_and_time = sorted(rootstreamdic[DataKey('Start and endtime')]) + runs = [x[0] for x in run_and_time] + runmin = min(runs) + runmax = max(runs) + + tmin = min([x[1][0] for x in run_and_time]) + tmax = max([x[1][1] for x in run_and_time]) + + # time structs + tminS = time.gmtime(tmin) + tmaxS = time.gmtime(tmax) + + # html references + htmlstr = [] + + # create graphs + graphs = [] + sortdic = {} + maxnev = { 'all_':-1, 'debug_':-1,'calibration_':-1 } + for key, content in rootstreamdic.iteritems(): + if type(key)==DataKey: key = key.ResultKey + if not 'Start and endtime' in key: + graphs.append( TGraph( len(content) ) ) + graphs[-1].SetName( key.strip().replace(' ','_') ) + graphs[-1].SetTitle( key.strip() ) + cumulsum = 0 + for i in range(len(content)): + j = len(content)-1-i # reverse order + scale = 1.e6 + if 'debug_' in key: scale = 1.e3 + cumulsum += content[j][1]/scale + graphs[-1].SetPoint( i, content[j][0], cumulsum ) # number of events in million + for stype in maxnev.keys(): + if stype in key: + if cumulsum > maxnev[stype]: maxnev[stype] = cumulsum + if not 'debug_' in key and cumulsum > maxnev['all_']: maxnev['all_'] = cumulsum + sortdic[len(graphs)-1] = cumulsum + + # sort dictionary into list + sortedlist = sorted(sortdic, key=sortdic.__getitem__, reverse=True) + + # individual physics streams + ic = 0 + for i in sortedlist: + g = graphs[i] + name = g.GetTitle() + if 'physics_' in name or 'express_' in name: + x = Double(0) + y = Double(0) + g.GetPoint( g.GetN()-1, x, y ) + c, frame, legend = CreateCanvasAndFrame( runmin, runmax, tminS, tmaxS, y*1.1, '', 'million' ) + legend.SetX2( legend.GetX2() - 0.2 ); + legend.SetY1( legend.GetY2() - 0.05 ); + DrawGraph( g, colours[ic], 2, 1, legend, name.replace('physics_','').replace('express_','').replace('express','Express'), True, 'M', TText() ) + ic += 1 + + # redraw axes and update + frame.Draw("sameaxis") + c.Update() + + # make plot + fname = '%s/atlrunquery_%s.png' % (datapath, name) + c.Print( fname ) + del frame + + # reference in html + htmlstr.append( MakeHtml( ['Events in streams %s versus run' % name], [fname], True, 60, 5 ) ) + + # physics streams only ------------------------------------------------------------ + c, frame, legend = CreateCanvasAndFrame( runmin, runmax, tminS, tmaxS, maxnev['all_'], '', 'million' ) + ic = 0 + for i in sortedlist: + g = graphs[i] + name = g.GetTitle() + if '#Events (streamed)' in name: + DrawGraph( g, c_NovelBlue, 2, 2, legend, "Total number of streamed events" ) + elif '#Events' == name: + DrawGraph( g, c_NovelBlue, 5, 1, legend, "Total number of triggered events (excl. calib)", True, 'M', TText() ) + elif 'physics_' in name or 'express_' in name: + DrawGraph( g, colours[ic], 2, 1, legend, name.replace('physics_','').replace('express_','').replace('express','Express') ) + ic += 1 + + # redraw axes and update canvas + frame.Draw("sameaxis") + c.Update() + + # make plot + fname = '%s/atlrunquery_physicsstreams.png' % datapath + c.Print( fname ) + del frame + + # reference in html + htmlstr.append( MakeHtml( ['Events in physics streams versus run'], [fname], True, 60, 5 ) ) + + # the other types can be + # stream_type nice text unit + plottypes = { 'debug_' : [ 'Debug', 'thousand', 'k' ], + 'calibration_' : [ 'Calibration', 'million', 'M' ] } + + # debug and calibration streams ---------------------------------------------------- + for key, cprop in plottypes.iteritems(): + + c, frame, legend = CreateCanvasAndFrame( runmin, runmax, tminS, tmaxS, maxnev[key], cprop[0], cprop[1] ) + legend.SetY1( legend.GetY1() + 0.15 ); + legend.SetX2( legend.GetX2() - 0.15 ); + ic = 0 + for i in sortedlist: + g = graphs[i] + name = g.GetTitle() + if key in name: + DrawGraph( g, colours[ic], 2, 1, legend, name, ic == 0, cprop[2], TText() ) + ic += 1 + + # redraw axes and update canvas + frame.Draw("sameaxis") + c.Update() + + # make plot + fname = '%s/atlrunquery_%sstreams.png' % (datapath, cprop[0].lower()) + c.Print( fname ) + del frame + + # reference in html + htmlstr.append( MakeHtml( ['Events in %s streams versus run' % cprop[0].lower()], [fname], True, 60, 5 ) ) + + # html code --------------------------------------------------------------------------- + retstr = '<table width="900" style="margin-left: 14px; background-color: #f0f0f0; border: 2px white solid; border-collapse: collapse;"><tr>' + for i,s in enumerate(htmlstr): + retstr += '<td style="padding: 4px">%s</td>' % s + if (i+1)%5 == 0: retstr += '</tr><tr>' + retstr += '</tr></table>' + + return retstr + +def MakeSummaryHtml( dic, dicsum, datapath ): + # run and time ranges + runs = dic[DataKey('Run')] + nruns = len(runs) + times = dic[DataKey('Start and endtime')] + + starttime = float(times[-1].partition(',')[0]) + endtime = float(times[0].partition(',')[2]) + + s = '<table style="color: #777777; font-size: 85%; margin-left: 14px" cellpadding="0" cellspacing="3">\n' + # runs in reverse order + s += '<tr><td><i>Number of runs selected:</i></td><td> %g</td><td></td></tr>\n' % (len(runs)) + s += '<tr><td><i>First run selected:</i></td><td> %s</td><td> (%s)</td></tr>\n' % (runs[-1], time.strftime("%a %b %d, %Y, %X",time.gmtime(starttime))) + s += '<tr><td><i>Last run selected:</i></td><td> %s</td><td> (%s)</td></tr>\n' % (runs[0], time.strftime("%a %b %d, %Y, %X",time.gmtime(endtime))) + s += '</table>\n' + + # -------------------------------------------------------------------------------------- + # run summary table + s += '<p></p>\n' + s += '<table style="margin-left: 14px">\n' + s += '<tr><td><b>Run / Event Summary</b></td></tr>\n' + s += '</table>\n' + + table = [] + # format Title in dico Title in table unit format which columns in table to exclude + keys = [ [ 'Duration', 'Run duration', ' h', '%.2f', [] ], + [ '#Events', '#Events (excl.)', '', 'pretty-int', [] ], + [ '#Events (streamed)', '#Events (incl.)', '', 'pretty-int', [] ], + [ 'STR:physics_*', 'Event size <font size="-2">(RAW)</font>',' MB', '%.2f', [0] ] # with wildcard + ] + for k in keys: + v = [] + for dk, c in dic.iteritems(): + # exact check? + Found = False + if ('*' in k[0] and k[0].replace('*','') in dk.ResultKey) or (not '*' in k[0] and k[0] == dk.ResultKey): + for x in c: + try: + v.append( DecodeStr( x, k[0] ) ) + except ValueError: # if n.a. + pass + # sanity check + + ### what if just n.a. in one column + ###if len(v) == 0: + ### print 'Big troubles... in function "AtlRunQuerySummary::MakeSummaryHtml": did not find key "%s" in dictionary' % k[0] + ### sys.exit(1) + table.append( ComputeStats( v ) ) + + s += MakeSummaryTableHtml( '', ['Total', 'Average', 'Min', 'Max'], keys, table, 500 ) + + # -------------------------------------------------------------------------------------- + # stream summary table + s += '<p></p>\n' + s += '<table style="margin-left: 14px">\n' + s += '<tr><td><b>Streams / Events Summary</b> <font size=-2> [ Straight averages given, not weighted by number of events in run ]</font></td></tr>\n' + s += '</table>\n' + + streamdic = {} + streamsizedic = {} + streamfracdic = {} + streamratedic = {} + rootstreamdic = {} + v = [] + for dk, c in dic.iteritems(): + # for root + if '#Events' in dk.ResultKey: + rootstreamdic[dk] = [] + for run,dataentries in zip(runs,c): + #for i in range(len(c)): + run = int(run) + data = dataentries[0].value # for events and time info there is only one entry in the list per run + try: + rootstreamdic[dk].append([run, int(data)]) + except ValueError: + if data != 'n.a.': rootstreamdic[dk].append([run, data]) + + if 'Start and endtime' in dk.ResultKey: + rootstreamdic[dk] = [] + for run,data in zip(runs,c): + if data != 'n.a.': rootstreamdic[dk].append([int(run), map(float,data.split(','))]) + + # collect streams + if 'STR:' in dk.ResultKey: + # loop over runs + sname = dk.ResultKey.replace('STR:','') + streamdic[sname] = [] + streamsizedic[sname] = 0 + streamfracdic[sname] = [] + streamratedic[sname] = [] + rootstreamdic[sname] = [] + #for i in range(len(c)): + for run,t,dataentries in zip(runs,times,c): + run = int(run) + data = dataentries[0].value # for stream data there is only one entry in the list per run + if not isinstance(data, str): # not 'n.a.' + streamdic[sname].append(data[0]) + streamfracdic[sname].append(data[0]) + tmin_ = float(t.partition(',')[0]) + tmax_ = float(t.partition(',')[2]) + deltat = tmax_ - tmin_ + if (deltat > 0): streamratedic[sname].append(data[0]/deltat) + else : streamratedic[sname].append(-1) + streamsizedic[sname] += data[1] + try: + rootstreamdic[sname].append([run, data[0]]) + except ValueError: + pass + else: streamfracdic[sname].append('na') + + # sort (physics, debug, calibration) + streamtypes = ['physics', 'express', 'debug', 'calibration'] + sortedstreams = [] + for stype in streamtypes: + keys = {} + for key, c in streamdic.iteritems(): + if stype in key: keys[key] = sum(c) + sortedstreams += sorted(keys, key=keys.__getitem__, reverse=True) + + # fill table + keys = [] + table = [] + newstreamfracdic = {} + for stream in sortedstreams: + stats = ComputeStats( streamdic[stream] ) + table.append( stats[0:4] ) + + # compute event fractions (not for calibration streams) + if not 'calibration_' in stream: + v = [] + for i, evrun in enumerate(streamfracdic[stream]): + if evrun != 'na': + evsum = float(0) + for stream2 in sortedstreams: + if not 'calibration_' in stream2: + if streamfracdic[stream2][i] != 'na': evsum += streamfracdic[stream2][i] + if evsum > 0: v.append( float(evrun)/evsum*100 ) # fraction in percent + + table[-1] += ['%.3g' % ComputeStats( v )[1]] + else: + table[-1] += ['&minus'] + + # average rate + table[-1] += ['%.3g' % ComputeStats( streamratedic[stream] )[1]] + + # put here #runs where the stream was produced + table[-1] += [stats[4]] + + # event sizes + if table[-1][0] > 0: table[-1] += [filesize(streamsizedic[stream]), + filesize(float(streamsizedic[stream])/table[-1][0])] + else: table[-1] += [filesize(streamsizedic[stream]),'&minus'] + + prettystr = stream + if 'debug' in stream: prettystr = '<font color="#C11B17">' + stream + '</font>' + # format Title in dico Title in table unit format which columns in table to exclude + keys.append( [stream, prettystr, '', 'pretty-int', []] ) + + s += MakeSummaryTableHtml( 'Stream', + ['Total', 'Average', 'Min', 'Max', + 'Average<br>fraction (%)', + 'Average<br>rate (Hz)', + '#Runs<br><font size="-2">(out of %i)</font>' % nruns, + 'Total file<br>size <font size="-2">(RAW)</font>', + 'Average event<br>size <font size="-2">(RAW)</font>'], keys, table, 900 ) + + # make integrated events plots + htmlstr = MakeRootPlots( rootstreamdic, datapath ) + s += htmlstr + + return s + + # DQ summary not operational anymore since defects ? + + # -------------------------------------------------------------------------------------- + # DQ summary table + s += '<p></p>\n' + s += '<table style="margin-left: 14px">\n' + s += '<tr><td><b>Data Quality Summary - SHIFTOFL (number of runs)</b> <font size=-2> [ No number signifies zero. Move mouse over number to see corresponding runs. ]</font></td></tr>\n' + s += '</table>\n' + + dqflags = ['G','Y','R','B','U','n.a.'] + dqflagsNames = ['Green', 'Yellow', 'Red', 'Black', 'Unknown', 'Not assigned '] + dqitems = DQChannels() + + # DQ flags are grouped together according to their channel numbers + # all groups with equal first two digits belong together + + UseGroups = False + if UseGroups: + ndq = {} + for chan, dqgroup in DQGroupDict.items(): + ndq[chan] = {} + for dqflag in dqflags: + ndq[chan][dqflag] = 0 + + for chan, dqchan in dqitems: + if dic.has_key( dqchan ): + grchan = int(chan/10)*10 + if grchan == 430: grchan = 420 # 'ad hoc' correction for TAU trigger + for dqflag in dic[dqchan]: + ndq[grchan][dqflag] += 1 + + colstr = [] + colchans = [] + for chan, dqgroup in DQChannelDict.items(): + colstr.append( dqgroup ) + colchans.append( chan ) + + # here per channel + colstr = [] + colchans = [] + ndq = {} + for chan, dqchan in dqitems: + dqchan = "SHIFTOFL_%s" % (dqchan) + colstr.append( dqchan ) + colchans.append( chan ) + + ndq[chan] = {} + for dqflag in dqflags: + ndq[chan][dqflag] = [0, []] + + if dic.has_key( dqchan ): + for i, dqflag in enumerate(dic[dqchan]): + ndq[chan][dqflag][0] += 1 + ndq[chan][dqflag][1].append(runs[i]) + + + # create summary table + # break table after 'nc' entries + nc = 15 + colstr_ = [] + colchans_ = [] + for i in range(len(colchans)): + if (i+1)%nc == 0: + labeloffset = i # required to avoid double-labels for tool-tips + colstr_ = [] + colchans_ = [] + else: + colstr_.append(colstr[i]) + colchans_.append(colchans[i]) + + # groups + labeloffset = 0 + for dqgroup, content in DQSuperGroupsDict.iteritems(): + colstr_ = [] + colchans_ = [] + title = content[0] + for dqchan in content[1]: + colstr_.append(dqchan) + colchans_.append(DQChannelDict[dqchan]) + s += MakeDQSummaryTableHtml( '', title, colstr_, colchans_, dqflags, dqflagsNames, ndq, nruns, labeloffset ) + labeloffset += 1 # required to avoid double-labels for tool-tips + s += '<p></p>' + + # return full page string + return s diff --git a/Database/CoolRunQuery/python/html/__init__.py b/Database/CoolRunQuery/python/html/__init__.py new file mode 100644 index 00000000000..74583d364ec --- /dev/null +++ b/Database/CoolRunQuery/python/html/__init__.py @@ -0,0 +1,2 @@ +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + diff --git a/Database/CoolRunQuery/python/output/AtlRunQueryRoot.py b/Database/CoolRunQuery/python/output/AtlRunQueryRoot.py new file mode 100644 index 00000000000..e1e8058e6dc --- /dev/null +++ b/Database/CoolRunQuery/python/output/AtlRunQueryRoot.py @@ -0,0 +1,975 @@ +#!/bin/env python + +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration +# +# ---------------------------------------------------------------- +# Script : AtlRunQueryRoot.py +# Project: AtlRunQuery +# Purpose: ROOT interactions +# Authors: Andreas Hoecker (CERN), Joerg Stelzer (DESY) +# Created: Dec 13, 2008 +# ---------------------------------------------------------------- +# +# --------------------------------------------------------------------------------------------------- +# ROOT TTree making and plotting +# --------------------------------------------------------------------------------------------------- + +import datetime, sys, os +from array import array +from CoolRunQuery.utils.AtlRunQueryUtils import importroot +importroot() # needed before any root import to switch to batch mode +from ROOT import TFile, TTree, gDirectory, TCanvas, gPad, TGraph, TColor, TStyle, TGaxis, gROOT, gStyle, TText, TH1F, TLegend, TDatime, TF1 + +# --------------------------------------------------------------------------------------------------- +# Creation of ROOT output file +# --------------------------------------------------------------------------------------------------- + +# general style +def SetStyle( whiteCanvas = False ): + + # colors + c_Canvas = TColor.GetColor( "#f0f0f0" ) # TColor.GetColor( "#e9e6da" ) + if whiteCanvas: c_Canvas = TColor.GetColor( 10 ) + c_FrameFill = 10 + c_TitleBox = TColor.GetColor( "#dae1e6" ) + c_SignalLine = TColor.GetColor( "#0000ee" ) + c_SignalFill = TColor.GetColor( "#7d99d1" ) + c_BackgroundLine = TColor.GetColor( "#ff0000" ) + c_BackgroundFill = TColor.GetColor( "#ff0000" ) + + myStyle = TStyle( gROOT.GetStyle("Plain") ) + myStyle.SetName("myStyle") + + myStyle.SetLineStyleString( 5, "[52 12]" ) + myStyle.SetLineStyleString( 6, "[22 12]" ) + myStyle.SetLineStyleString( 7, "[22 10 7 10]" ) + + # pretty colors + myStyle.SetPalette(1) + + # use plain black on white colors + myStyle.SetFrameBorderMode(0) + myStyle.SetCanvasBorderMode(0) + myStyle.SetPadBorderMode(0) + myStyle.SetPadColor(0) + myStyle.SetFillStyle(0) + + myStyle.SetLegendBorderSize(0) + + myStyle.SetTitleFillColor( c_TitleBox ) + myStyle.SetFrameFillColor( c_FrameFill ) + myStyle.SetCanvasColor( c_Canvas ) + + # set the paper & margin sizes + myStyle.SetPaperSize(20,26) + myStyle.SetPadTopMargin(0.11) + myStyle.SetPadRightMargin(0.05) + myStyle.SetPadBottomMargin(0.11) + myStyle.SetPadLeftMargin(0.12) + + # use bold lines and markers + myStyle.SetMarkerStyle(21) + myStyle.SetMarkerSize(0.3) + myStyle.SetHistLineWidth(2) + myStyle.SetLineStyleString(2,"[12 12]") # postscript dashes + + # do not display any of the standard histogram decorations + myStyle.SetOptTitle(1) + myStyle.SetTitleH(0.052) + + myStyle.SetOptStat(0) + myStyle.SetOptFit(0) + + # put tick marks on top and RHS of plots + myStyle.SetPadTickX(1) + myStyle.SetPadTickY(1) + + # use this style + gROOT.GetListOfStyles().Add(myStyle) + gROOT.SetStyle("myStyle") + +def SaveGraphsToFile( filename, varnames, xvecs, yvecs, addparamName = None, addparamVal = None ): + f = TFile.Open( filename, 'RECREATE' ) + if len(xvecs) != len(yvecs) or len(varnames) != len(xvecs): + print 'ERROR: wrong dimensions in "AtlRunQueryRoot.SaveGraphsToFile"' + return + for ik in range(len(xvecs)): + xv = xvecs[ik] + yv = yvecs[ik] + g = TGraph( len(xv) ) + g.SetName ( "graph%i" % (ik+1) ) + g.SetTitle( varnames[ik] ) + for i in range(len(xv)): + g.SetPoint( i, xv[i], yv[i] ) + g.Write() + + # save additional parameter + if addparamName != None: + addparam = TF1( addparamName, "[0]", 0, 1 ) + addparam.SetParameter( 0, addparamVal ) + addparam.Write() + + f.Close() + +def MakePlots( tree, datapath ): + + # batch mode -> no windows + + gROOT.SetBatch( 1 ) + if tree == None: + print 'ERROR: input tree is None' + return + + # style -------------------------------------------- + steelBlue = TColor.GetColor( "#4863A0" ) + slateBlue = TColor.GetColor( "#574EC7" ) + dogerBlue = TColor.GetColor( "#1569C7" ) + dogerBlue3 = TColor.GetColor( "#1569C7" ) + graphCol = dogerBlue + markerCol = dogerBlue3 + + SetStyle() + # -------------------------------------------------- + + # reference + reflist = [ 'Run' ] + + # types that can be plotted + validtypes = [ 'int', 'float', 'double', 'short', 'long' ] + + # get list of leaves for plotting + leafList = tree.GetListOfLeaves() + varlist = [] + + for leaf in leafList: + if leaf != None: + # check type, only values are plotted + typename = leaf.GetTypeName().lower() + valid = False + for vt in validtypes: + if vt in typename: valid = True + if valid: varlist.append( leaf.GetName() ) + + # plot + TGaxis.SetMaxDigits(6) + c = [] + g = [] + fnames = [] + hnames = [] + for ref in reflist: + # sanity check + if not ref in varlist: + print 'Big troubles in "MakePlots" --> reference variable "%s" not in TTree' % ref + sys.exit(1) + + for var in varlist: + if var == ref: continue + + vreg = var.replace('-','_') + rreg = ref.replace('-','_') + + hname = "h_%s_%s" % (ref, var) + c.append( TCanvas( "c_%s" % hname, "ATLAS Run Query: %s vs. %s" % (var, ref), 0, 0, 600, 500 ) ) + tree.Draw( "%s:%s" % (vreg, rreg), "", "goff" ) + g.append( TGraph( tree.GetSelectedRows(), tree.GetV2(), tree.GetV1() ) ) + g[-1].SetTitle( "ATLAS Run Query: %s vs. %s" % (var, ref) ) + g[-1].SetMarkerStyle( 20 ) + g[-1].SetMarkerColor( markerCol ) + g[-1].SetMarkerSize( 1.2 ) + g[-1].SetLineWidth( 2 ) + g[-1].SetLineColor( graphCol ) + g[-1].GetXaxis().SetTitleOffset( 1.25 ) + g[-1].GetYaxis().SetTitleOffset( 1.75 ) + g[-1].GetXaxis().SetTitle( ref ) + g[-1].GetYaxis().SetTitle( var ) + g[-1].GetXaxis().SetLabelOffset( 0.012 ) + g[-1].GetYaxis().SetLabelOffset( 0.012 ) + g[-1].GetXaxis().SetNdivisions( 506 ) + + # some global style settings + scale = 1 + gPad.SetTicks() + gPad.SetLeftMargin ( 0.138*scale ) + gPad.SetRightMargin ( 0.050*scale ) + gPad.SetBottomMargin( 0.120*scale ) + + g[-1].Draw("alp") + + c[-1].Update() + fnames.append( '%s/atlrunquery_%s.png' % (datapath, hname) ) + hnames.append( '%s versus %s' % (var, ref ) ) + c[-1].Print( fnames[-1] ) + + return hnames, fnames + +# --------------------------------------------------------------------------------------------------- +# Creation of ROOT output file +# --------------------------------------------------------------------------------------------------- + +def makeLBPlotSummaryForLHC( xvec, xvecStb, yvec, runNr, datapath, printText = '' ): + + # sanity check + if not xvec or len(yvec)==0 or not yvec[0]: return "None" + + ylegend = ['Intensity Beam-1', 'Intensity Beam-2', 'Beam energy', 'Online inst. luminosity' ] + + SetStyle() + name = 'LHCsummary_vs_lb_run_%i' % (runNr) + #print "Attempt printing", name + title = 'LHC summary vs. LB for run_%i' % (runNr) + szescale = 1.2 + c = TCanvas( name, title, 0, 0, int(530*szescale), int(400*szescale) ) + c.GetPad(0).SetTopMargin(0.13) + c.GetPad(0).SetLeftMargin(0.09) + c.GetPad(0).SetRightMargin(1.3) + # c.GetPad(0).SetGrid() + x1 = xvec[0] + x2 = xvec[-1] + + h = TH1F( name, title, x2 - x1 + 1, x1, x2 + 1 ) # for stable beams + hg = [] + for iy in range(len(yvec)): + hg.append( TH1F( name + 'g%i' % iy, title, x2 - x1 + 1, x1, x2 + 1 ) ) + h.GetXaxis().SetTitle( 'Luminosity block number' ) + h.GetXaxis().SetTitleOffset( 1.3 ) + h.GetYaxis().SetTitle( 'Beam intensity (10^{11} protons)' ) + h.GetYaxis().SetTitleOffset( 1.0 ) + h.SetTitle( title ) + + for i in range(len(xvec)): + for iy,y in enumerate(yvec): + hg[iy].SetBinContent( xvec[i], y[i] ) + + # first draw histogram + ymax = max(hg[0].GetMaximum(),hg[1].GetMaximum()) + y2 = ymax*1.3 + if y2 <= 0: y2 = 1 + y1 = 0 + h.SetMinimum(0) + h.SetMaximum(y2) + h.Draw("0") + gPad.SetTicks(1, 0) + + # beam energy (rescale) + ebcol = TColor.GetColor( "#306754" ) + heb = hg[2] + heb.Scale( 1./1000.0 ) # in TeV + if heb.GetMaximum()==0: heb.SetMaximum(1) + ebmax = heb.GetMaximum()*1.3 + + # one more sanity check + if ebmax == 0: + return "None" + + heb.SetLineColor( ebcol ) + heb.SetLineWidth( 1 ) + scale = h.GetMaximum()/ebmax + # heb.SetFillColor( TColor.GetColor("#CCFB5D") ) + heb.SetFillColor( TColor.GetColor("#C3FDB8") ) + heb.Scale( scale ) + heb.Draw("same") + + if xvecStb: + for i in range(len(xvec)): + if xvec[i] in xvecStb: h.SetBinContent( xvec[i], yvec[2][i]*scale/1000.0 ) # draw for beam energy (in TeV) + h.SetFillColor( TColor.GetColor( "#63bD58" ) ) + h.SetFillStyle( 3007 ) + h.SetLineColor( TColor.GetColor( "#C3FDB8" ) ) + h.SetLineWidth( 1 ) + h.SetLineStyle( 1 ) + h.Draw("same") + hebp = TH1F( heb ) + hebp.SetFillColor( 0 ) + hebp.Draw("same") + + + colorList = [TColor.GetColor( "#255EC7" ), TColor.GetColor( "#E42217" ), TColor.GetColor( "#2212EE" ), TColor.GetColor( "#22EE33" )] + + for ig in range(2): + col = 1 + if ig < len(colorList): hg[ig].SetLineColor( colorList[ig] ) + hg[ig].SetLineWidth( 2 ) + hg[ig].Draw("same") + + # logo + now = str(datetime.datetime.today()) + t = TText( h.GetXaxis().GetXmax(), y2*1.01, 'ATLAS Run Query %s' % now[:now.find('.')] ) + t.SetTextAlign( 31 ) + t.SetTextSize( 0.035 ) + t.SetTextSize( 0.030 ) + t.SetTextColor( TColor.GetColor( "#888888" ) ) + t.Draw() + + drawLegend = False + dx = 1 - c.GetRightMargin() - c.GetLeftMargin() - 0.05 + dy = 0.1 + legend = TLegend( 1 - c.GetRightMargin() - dx, 1 - c.GetTopMargin() - dy - 0.035, + 1 - c.GetRightMargin(), 1 - c.GetTopMargin() - 0.035) + # legend.SetFillStyle( 1001 ) + # legend.SetFillColor( TColor.GetColor( "#FFFFFF" ) ) + legend.SetNColumns(2) + legend.SetBorderSize( 1 ) + legend.SetMargin( 0.15 ) + legend.SetLineColor( 0 ) + legend.SetTextColor( 1 ) + for ig,hgg in enumerate(hg): + if ylegend[ig]: + opt = "l" + if ig == 2: opt = "FL" + legend.AddEntry( hgg, ylegend[ig], opt ) + drawLegend = True + + if xvecStb: + legend.AddEntry( h, "LBs with stable beams","F" ) + drawLegend = True + if drawLegend: legend.Draw("same") + + # redraw axis + h.Draw( "sameaxis" ) + + # draw beam energy axis on the right side + if h.GetMaximum() > 0: + axis = TGaxis( x2 + 1, 0, x2 + 1, h.GetMaximum(), 0, ebmax, 510, "+L" ) + axis.SetLabelOffset( 0.007 ) + axis.SetTitleOffset( 1.2 ) + axis.SetTitleSize( h.GetXaxis().GetTitleSize() ) + axis.SetLabelSize( 0.04 ) + axis.SetLineColor( ebcol ) + axis.SetTitleColor( ebcol ) + axis.SetLabelColor( ebcol ) + axis.SetTitle( "Beam energy (TeV)" ) + axis.Draw() + + c.Update() + fnames = '%s/atlrunquery_%s.png' % (datapath, name) + c.Print( fnames ) + #print "Printing",fnames + + return fnames + +def makeTimePlotList( xvec, xveclb, yvec, toffset, dt, ymin, ymax, + xtit, ytit, ylegend, name, title, datapath, printText = '' ): + + # sanity check + if not xvec or len(yvec)==0 or not yvec[0]: return "None" + + SetStyle() + + gStyle.SetPadTickX(0) + gStyle.SetPadTickY(1) + + c = TCanvas( name, title, 0, 0, 530, 430 ) + c.GetPad(0).SetTopMargin(0.22) + c.GetPad(0).SetGrid() + + # create the graphs + graphs = [] + y1 = 1e30 + y2 = -1e30 + for ig in range(len(yvec)): + n = len(yvec[ig]) + g = TGraph( n ) + for i in range(n): + g.SetPoint( i, xvec[ig][i], yvec[ig][i] ) + if ymin == ymax: + y1 = min(y1,yvec[ig][i]) + y2 = max(y2,yvec[ig][i]) + graphs.append( g ) + + if ymin != ymax: + y1 = ymin + y2 = ymax + + # create the frame + frame = TH1F( name, title, int(dt/2), 0, dt ) + + # x-axis is time format + frame.GetXaxis().SetTimeDisplay(1) + # use rather default: frame.GetXaxis().SetTimeFormat("%d-%Hh") + + # ================================= + # toffset += 7*3600 # BUG in ROOT ??? + # ================================= + + frame.GetXaxis().SetTimeOffset(long(toffset), "gmt") # assumes time given was local + + frame.GetXaxis().SetTitle( xtit ) + frame.GetXaxis().SetTitleOffset( 1.3 ) + frame.GetYaxis().SetTitle( ytit ) + frame.GetYaxis().SetTitleOffset( 1.4 ) + frame.SetTitle( title ) + frame.SetMinimum(y1 - (y2-y1)*0.1) + frame.SetMaximum(y2 + (y2-y1)*0.3) + frame.Draw() + + colorList = [TColor.GetColor( "#154EB7" ), TColor.GetColor( "#82CAFF" ), TColor.GetColor( "#D11B17" ), TColor.GetColor( "#FF9A4D" )] + styleList = [1,1,1,1] + + for ig,g in enumerate(graphs): + if ig < len(colorList): + g.SetLineColor( colorList[ig] ) + g.SetLineStyle( styleList[ig] ) + g.SetLineWidth( 2 ) + g.Draw("same") + + if printText: + t = TText( frame.GetXaxis().GetXmax(), y2*1.01, printText ) + t.SetTextAlign( 31 ) + t.SetTextSize( 0.035 ) + t.Draw() + + # logo + now = str(datetime.datetime.today()) + tlogo = TText( frame.GetXaxis().GetXmax()*1.025 - frame.GetXaxis().GetXmin()*0.025, y1, + 'ATLAS Run Query %s' % now[:now.find('.')] ) + tlogo.SetTextSize( 0.030 ) + tlogo.SetTextColor( TColor.GetColor( "#aaaaaa" ) ) + tlogo.SetTextAngle(90) + tlogo.Draw() + + # axis for LBs + lbcol = TColor.GetColor( "#777777" ) + axis = TGaxis( frame.GetXaxis().GetXmin(), frame.GetMaximum(), + frame.GetXaxis().GetXmax(), frame.GetMaximum(), + 1, len(xveclb), 505, "-L" ) + axis.SetLabelOffset( frame.GetXaxis().GetLabelOffset() ) + axis.SetTitleOffset( 1.4 ) + axis.SetTitleSize( frame.GetXaxis().GetTitleSize() ) + axis.SetLabelSize( frame.GetXaxis().GetLabelSize() ) + axis.SetLineColor( lbcol ) + axis.SetTitleColor( lbcol ) + axis.SetLabelColor( lbcol ) + axis.SetTitle( "Luminosity block number (indicative)" ) + axis.Draw() + + # legend + drawLegend = False + dx = 1 - c.GetRightMargin() - c.GetLeftMargin() - 0.05 + dy = 0.1 + legend = TLegend( 1 - c.GetRightMargin() - dx, 1 - c.GetTopMargin() - dy - 0.035, + 1 - c.GetRightMargin(), 1 - c.GetTopMargin() - 0.035) + # legend.SetFillStyle( 1001 ) + # legend.SetFillColor( TColor.GetColor( "#FFFFFF" ) ) + legend.SetNColumns(2) + legend.SetBorderSize( 1 ) + legend.SetMargin( 0.15 ) + legend.SetLineColor( 0 ) + legend.SetTextColor( 1 ) + legend.SetTextSize( 0.037 ) + for ig,g in enumerate(graphs): + if ylegend[ig]: + h = g.GetHistogram() + h.SetLineColor( colorList[ig] ) + h.SetLineStyle( styleList[ig] ) + h.SetLineWidth( 2 ) + legend.AddEntry( h, ylegend[ig], "l" ) + drawLegend = True + if drawLegend: legend.Draw("same") + + # redraw axis + frame.Draw( "sameaxis" ) + + c.Update() + fnames = '%s/atlrunquery_%s.png' % (datapath, name) + c.Print( fnames ) + + return fnames + + +def makeLBPlotList( xvec, xvecStb, yvec, xtitle, ytitle, ylegend, histname, histtitle, datapath, printText = '', ymin = None , ymax = None ): + + # sanity check + if not xvec or len(yvec)==0 or not yvec[0]: return "None" + + SetStyle() + c = TCanvas( histname, histtitle, 0, 0, 530, 400 ) + c.GetPad(0).SetTopMargin(0.13) + c.GetPad(0).SetGrid() + x1 = xvec[0] + x2 = xvec[-1] + + h = TH1F( histname, histtitle, x2 - x1 + 1, x1, x2 + 1 ) # for stable beams + hg = [] + for iy in range(len(yvec)): + hg.append( TH1F( histname + 'g%i' % iy, histtitle, x2 - x1 + 1, x1, x2 + 1 ) ) + h.GetXaxis().SetTitle( xtitle ) + h.GetXaxis().SetTitleOffset( 1.3 ) + h.GetYaxis().SetTitle( ytitle ) + h.GetYaxis().SetTitleOffset( 1.4 ) + h.SetTitle( histtitle ) + + + for i in range(len(xvec)): + for iy,y in enumerate(yvec): + if type(y[i]) == tuple: + val,valerr = y[i] + else: + val,valerr = y[i],0 + hg[iy].SetBinContent( xvec[i], val ) + hg[iy].SetBinError( xvec[i], valerr ) + if xvec[i] in xvecStb: h.SetBinContent( xvec[i], yvec[0][i] ) + + if not ymax: + ymax = max([h.GetMaximum() for h in hg]) + if ymax <= 0: ymax = 1 + ymax *= 1.2 + + if not ymin: + ymin=0 + + # first draw histogram + h.SetMinimum(ymin) + h.SetMaximum(ymax) + if xvecStb: + h.SetFillColor( TColor.GetColor( "#98AFC7" ) ) + h.SetLineColor( TColor.GetColor( "#788FA7" ) ) + h.SetLineWidth( 1 ) + h.SetLineStyle( 1 ) + h.Draw("") + else: + h.Draw("0") + + colorList = [TColor.GetColor( "#255EC7" ), TColor.GetColor( "#E42217" ), TColor.GetColor( "#2212EE" ), TColor.GetColor( "#22EE33" )] + + for ig,hgg in enumerate(hg): + if ig < len(colorList): hgg.SetLineColor( colorList[ig] ) + hgg.SetLineWidth( 2 ) + hgg.Draw("e2same") + + if printText: + t = TText( h.GetXaxis().GetXmax(), (ymax-ymin)*1.01 + ymin, printText ) + t.SetTextAlign( 31 ) + t.SetTextSize( 0.035 ) + t.Draw() + + # logo + now = str(datetime.datetime.today()) + tlogo = TText( h.GetXaxis().GetXmax()*1.025, (ymax-ymin)*0.0+ymin, 'ATLAS Run Query %s' % now[:now.find('.')] ) + tlogo.SetTextSize( 0.030 ) + tlogo.SetTextColor( TColor.GetColor( "#888888" ) ) + tlogo.SetTextAngle(90) + tlogo.Draw() + + drawLegend = False + dx = 0.4 + dy = 0.10 + 0.03*(len(hg)-1) + legend = TLegend( 1 - c.GetRightMargin() - dx, 1 - c.GetTopMargin() - dy - 0.015, + 1 - c.GetRightMargin(), 1 - c.GetTopMargin() - 0.015) + # legend.SetFillStyle( 1001 ) + # legend.SetFillColor( TColor.GetColor( "#FFFFFF" ) ) + legend.SetBorderSize( 1 ) + legend.SetMargin( 0.15 ) + legend.SetLineColor( 0 ) + legend.SetTextColor( 1 ) + for ig,hgg in enumerate(hg): + if ylegend[ig]: + legend.AddEntry( hgg, ylegend[ig], "l" ) + drawLegend = True + + if xvecStb: + legend.AddEntry( h, "LBs with stable beams","F" ) + drawLegend = True + if drawLegend: legend.Draw("same") + + # redraw axis + h.Draw( "sameaxis" ) + + c.Update() + fnames = '%s/atlrunquery_%s.png' % (datapath, histname) + c.Print( fnames ) + + return fnames + + +def makeBSPlots( xvec, yvec, xtitle, ytitle, histname, histtitle, datapath, ymin, ymax, printText = '' ): + + # sanity check + if not xvec or len(yvec)==0 or not yvec[0]: + return None + + ROOT = importroot() + + SetStyle() + c = TCanvas( histname, histtitle, 0, 0, 530, 400 ) + c.GetPad(0).SetTopMargin(0.13) + c.GetPad(0).SetGrid() + xlower = 1 + xupper = sum(xvec) + + bounds = [1] + for nlb in xvec: bounds += [bounds[-1]+nlb] + + from array import array + h = TH1F( histname + 'g', histtitle, len(bounds)-1, array('f',bounds) ) + h.GetXaxis().SetTitle( xtitle ) + h.GetXaxis().SetTitleOffset( 1.3 ) + h.GetYaxis().SetTitle( ytitle ) + h.GetYaxis().SetTitleOffset( 1.4 ) + h.SetTitle( histtitle ) + h.SetMinimum(ymin) + h.SetMaximum(ymax) + h.SetFillColor( TColor.GetColor( "#56A5EC" ) ) + for bin,y in enumerate(yvec): + val,valerr = y + h.SetBinContent( bin+1, val ) + h.SetBinError ( bin+1, valerr ) + h.Draw("e2") + + hc = h.Clone() + hc.SetLineColor( ROOT.kBlack ) + hc.SetLineWidth(2) + hc.Draw("e0same") + + if printText: + t = TText( h.GetXaxis().GetXmax(), (ymax-ymin)*1.01 + ymin, printText ) + t.SetTextAlign( 31 ) + t.SetTextSize( 0.035 ) + t.Draw() + + # logo + now = str(datetime.datetime.today()) + tlogo = TText( h.GetXaxis().GetXmax()*1.025, (ymax-ymin)*0.0+ymin, 'ATLAS Run Query %s' % now[:now.find('.')] ) + tlogo.SetTextSize( 0.030 ) + tlogo.SetTextColor( TColor.GetColor( "#888888" ) ) + tlogo.SetTextAngle(90) + tlogo.Draw() + + c.Update() + fnames = '%s/atlrunquery_%s.png' % (datapath, histname) + c.Print( fnames ) + + return fnames + + +def makeLBPlot( xvec, xvecStb, yvecScalar, + xtitle, ytitle, ylegendScalar, + histname, histtitle, + datapath, printText = '', ymin = None, ymax = None ): + yvec = [yvecScalar] + ylegend = [ylegendScalar] + return makeLBPlotList( xvec, xvecStb, yvec, xtitle, ytitle, ylegend, histname, histtitle, datapath, printText, ymin, ymax ) + +def InttypeTrf( tree, var, vlist, value, kcoord ): + # initialisation + if kcoord < 0: + vlist.append( array( 'i', [ 0 ] ) ) + vreg = var.replace('-','_') + tree.Branch( vreg, vlist[-1], "%s/I" % vreg ) + return + # fill tree + try: vlist[kcoord][0] = int(value) + except: vlist[kcoord][0] = -1 + +def FloattypeTrf( tree, var, vlist, value, kcoord ): + # initialisation + if kcoord < 0: + vlist.append( array( 'f', [ 0 ] ) ) + vreg = var.replace('-','_') + tree.Branch( vreg, vlist[-1], "%s/F" % vreg ) + return + # fill tree + try: vlist[kcoord][0] = float(value) + except: vlist[kcoord][0] = -1 + +def StringtypeTrf( tree, var, vlist, value, kcoord ): + # initialisation + if kcoord < 0: + vlist.append( array( 'c', 'ab\0' ) ) + tree.Branch( var, vlist[-1], "%s/C" % var ) + return + # fill tree + try: + vlist[kcoord] = array( 'c', value + '\0' ) + tree.SetBranchAddress( var, vlist[kcoord] ) + except: + vlist[kcoord] = array( 'c', 'unknown\0' ) + tree.SetBranchAddress( var, vlist[kcoord] ) + +def TimetypeTrf( tree, var, vlist, value, kcoord ): + return StringtypeTrf( tree, var, vlist, value, kcoord ) + +def DurationtypeTrf( tree, var, vlist, value, kcoord ): + # original format: '[2d] 13h 15m 45s' --> transform in seconds + if kcoord >= 0: + try: + dur = value.split() + if len(dur) == 3: + h, m, s = dur + d = '0d' + elif len(dur) == 4: + d, h, m, s = dur + else: + print 'Unknown format in "DurationtypeTrf:"' + print dur + sys.exit(1) + + value = str( ( (int(d.replace('d','').strip())*24 + int(h.replace('h','').strip()))*60 + + int(m.replace('m','').strip()) )*60 + int(s.replace('s','').strip()) ) + except: + print value + sys.exit(1) + value = 0 + InttypeTrf( tree, var, vlist, value, kcoord ) + + +def MakeHtml( hnames, fnames, uselarge = False, iconwidth = 70, nbreakline = 10 ): + windowstyle = 'openWindow' + if uselarge: + windowstyle = 'openLargeWindow' + iconwidth = 120 + s = '' + s += '<table><tr>' + for i in range(0,len(fnames)): + f = fnames[i] + h = hnames[i] + s += """<td valign="top"><a STYLE="text-decoration: none" href=" """ + s += """javascript:%s('Plot','<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html xmlns:my><head><title>%s</title><LINK href="atlas-runquery.css" rel="stylesheet" type="text/css"><body><table style="font-family: sans-serif; font-size: 85%%"><tr><td valign="top">""" % (windowstyle,h) + s += """<img src="%s">""" % f + s += """<hr color="red" size=1><font color="#777777"><font size="-1"><i><font size="-2">Created by AtlRunQuery on: %s</font></i></font></td></tr></table><script type="text/javascript"></script></body></html>""" % str(datetime.datetime.now()) + s += """')""" + s += """ "><img vspace=0 src="%s" title="Plot showing query result for %s" width="%i"></a><br><font size="-2">%s</font></td>""" % (f,h,iconwidth,h) + s += """<td> </td>""" + if (i+1)%nbreakline == 0: s += '</tr><tr>' + + s += '</tr></table>' + return s + +def GetActions(): + # define all actions (choose small letters) + # format: 'column header' 'include' 'type' '...' + actions = { "run": ( True, InttypeTrf ), + "links": ( False, StringtypeTrf ), + "#lb": ( True, InttypeTrf ), + "#events": ( True, InttypeTrf ), + "smk": ( True, InttypeTrf ), + "start and endtime": ( True, TimetypeTrf ), + "duration": ( True, DurationtypeTrf ), + "torcurrent": ( True, FloattypeTrf ), + "solcurrent": ( True, FloattypeTrf ), + "detector systems": ( False, StringtypeTrf ), + "stream": ( True, InttypeTrf ), + "lumi": ( True, FloattypeTrf ), + "other": ( True, StringtypeTrf ) + } + return actions + +def CreateRootFile( dic ): + + from CoolRunQuery.selector.AtlRunQuerySelectorBase import DataKey + + from CoolRunQuery.AtlRunQueryQueryConfig import QC + datapath = QC.datapath + + actions = GetActions() + + if not 'data' in os.listdir('.'): + os.mkdir('data') + + f = open( '%s/dummyroot.txt' % datapath, 'w' ) + + froot = TFile( '%s/atlrunquery.root' % datapath, 'RECREATE' ) + + tree = TTree( 'RunQuery', 'ATLAS Run Query' ) + + varlist = [] + + # create branches + r_1 = -1 + keylist = [] + for data_key in dic: + + #print "DATA_KEY",type(data_key),data_key + f.write( 'key: %s \n' % (data_key.ResultKey)) + + # the actual variable name used in the tree + var = data_key.ResultKey.replace('#',' ').strip().replace(' ','_').replace('STR:','').replace('-','_').replace(':','_') + + k = data_key.ResultKey.lower() + if data_key.Type==DataKey.STREAM: k = 'stream' + elif 'olc_' == data_key.ResultKey[0:4]: k = 'lumi' + + # is key included in 'actions' dictionary ? + if not k in actions: k = 'other' + + useit, function = actions[k] + if useit: + function( tree, var, varlist, -1, -1 ) # create branch + keylist.append( (data_key,var, function) ) + + # loop over runs + for ev in range(len(dic[DataKey('Run')])): + + for k,(data_key,var,function) in enumerate(keylist): + + val = dic[data_key][ev] + if data_key.Type==DataKey.STREAM: + val = val[0] + + function( tree, var, varlist, val, k ) + + tree.Fill() + + # make plots ? + hnames, fnames = MakePlots( tree, datapath ) + htmlstr = MakeHtml( hnames, fnames ) + + f.close() + tree.Write() + froot.Close() + + return froot.GetName(), htmlstr + + + +def makeRatePlot( v, lbduration, plottriggers, averrate, xtit, ytit, name, title, datapath, printText = '' ): + + ## # pickle input + ## import pickle + ## store = (v, lbduration, plottriggers, averrate, xtit, ytit, name, title, datapath, printText) + ## + ## pf = open( '%s/rates.pickle' % datapath, 'w' ) + ## try: pickle.dump(store, pf) + ## except: + ## print 'ERROR: could not pickle rates' + ## sys.exit(1) + ## pf.close() + + ROOT = importroot() + + SetStyle() + c = TCanvas( name, title, 0, 0, 530, 400 ) + c.GetPad(0).SetTopMargin(0.13) + c.GetPad(0).SetGrid() + + firstlb = min([lbinfo[0][0] for lbinfo in v.values()]) + lastlb = max([lbinfo[-1][0] for lbinfo in v.values()]) + + frame = TH1F( name, title, lastlb-firstlb+1, firstlb, lastlb+1 ) + frame.GetXaxis().SetTitle( xtit ) + frame.GetXaxis().SetTitleOffset( 1.3 ) + frame.GetYaxis().SetTitle( ytit ) + frame.GetYaxis().SetTitleOffset( 1.4 ) + + + hTAP = {} # trigger after prescale (not realy, it contains (tap+tav)/2 with and error of (tap-tav)/2) + hTAV = {} # trigger after veto + ymax = -1 + + # see http://root.cern.ch/root/html526/TColorWheel.html + colorList = [ (ROOT.kBlue,ROOT.kAzure+1), + (ROOT.kGreen+2,ROOT.kGreen+2), + (ROOT.kRed+1,ROOT.kRed), + (ROOT.kTeal-1,ROOT.kTeal), + (ROOT.kOrange+8,ROOT.kOrange), + (ROOT.kYellow+1,ROOT.kYellow), + (ROOT.kOrange+3,ROOT.kOrange+5), + (ROOT.kPink+8,ROOT.kPink+9) ] + + clrIdx = 0 + for trname,avrate in plottriggers: + hTAP[trname] = TH1F( '%s_ap_%s' % (name,trname), title, lastlb-firstlb+1, firstlb, lastlb+1 ) + hTAV[trname] = TH1F( '%s_av_%s' % (name,trname), title, lastlb-firstlb+1, firstlb, lastlb+1 ) + hap = hTAP[trname] + hav = hTAV[trname] + hav.SetLineWidth( 2 ) + if clrIdx < len(colorList): + dark,bright = colorList[clrIdx] + hav.SetLineColor( dark ) + hap.SetFillColor( bright ) + hap.SetMarkerColor( bright ) + clrIdx += 1 + + counts = v[trname] + for (lb,bp,ap,av),(lb2,lbstart,dt) in zip(counts,lbduration): + _apr = float(ap)/dt + _avr = float(av)/dt + hap.SetBinContent( lb-firstlb+1, (_apr+_avr)/2 ) + hap.SetBinError( lb-firstlb+1, (_apr-_avr)/2 ) + hav.SetBinContent( lb-firstlb+1, _avr ) + ymax = max(ymax,_apr) + + + if ymax <= 0: ymax = 1 + + # first draw histogram + y2 = ymax*1.2 + y1 = 0.01 + frame.SetMinimum(y1) + frame.SetMaximum(y2) + frame.Draw("0") + + for h in hTAP.values(): + h.Draw("same e2") + for h in hTAV.values(): + h.Draw("same") + + if printText: + t = TText( frame.GetXaxis().GetXmax(), y2*1.01, printText ) + t.SetTextAlign( 31 ) + t.SetTextSize( 0.035 ) + t.Draw() + + # logo + now = str(datetime.datetime.today()) + tlogo = TText( frame.GetXaxis().GetXmax()*1.025, y1, 'ATLAS Run QUERY %s' % now[:now.find('.')] ) + tlogo.SetTextSize( 0.030 ) + tlogo.SetTextColor( TColor.GetColor( "#888888" ) ) + tlogo.SetTextAngle(90) + tlogo.Draw() + + dx = 0.4 + dy = 0.10 + 0.01*(len(hTAV)-1) + legend = TLegend( 1 - c.GetRightMargin() - dx, 1 - c.GetTopMargin() - dy - 0.015, + 1 - c.GetRightMargin(), 1 - c.GetTopMargin() - 0.015) + legend.SetNColumns(2) + legend.SetBorderSize( 0 ) + legend.SetMargin( 0.15 ) + legend.SetLineColor( 0 ) + legend.SetTextColor( 1 ) + # fill legend - highest average rate first + for trname,avtrrate in averrate: + if not trname in hTAV: continue + legend.AddEntry( hTAV[trname], trname, "l" ) + legend.Draw("same") + + # redraw axis + frame.Draw( "sameaxis" ) + + c.Update() + fnames = '%s/atlrunquery_%s.png' % (datapath, name) + c.Print( fnames ) + frame.SetMaximum(ymax*5) + c.SetLogy(1) + c.Update() + fnames2 = '%s/atlrunquery_%s_log.png' % (datapath, name) + c.Print( fnames2 ) + + return fnames + + + + + +# command line driver for convenience +if __name__=='__main__': + + import pickle + datapath = '/afs/cern.ch/user/s/stelzer/runsummary/data' + pf = open( '%s/rates.pickle' % datapath, 'r' ) + try: store = pickle.load(pf) + except: + print 'ERROR: could not load rates' + sys.exit(1) + pf.close() + + (v, lbduration, plottriggers, averrate, xtit, ytit, name, title, datapath, printText) = store + + datapath = '/afs/cern.ch/user/s/stelzer/runsummary/data' + makeRatePlot(v, lbduration, plottriggers, averrate, xtit, ytit, name, title, datapath, printText) + + #froot = TFile.Open( 'atlrunquery.root', 'READ' ) + #tree = gDirectory.Get( 'RunQuery' ) + # + #hnames, fnames = MakePlots( tree ) + #htmlstr = MakeHtml( hnames, fnames ) + #print htmlstr diff --git a/Database/CoolRunQuery/python/output/AtlRunQuerySave.py b/Database/CoolRunQuery/python/output/AtlRunQuerySave.py new file mode 100644 index 00000000000..5f20251e2b8 --- /dev/null +++ b/Database/CoolRunQuery/python/output/AtlRunQuerySave.py @@ -0,0 +1,152 @@ +#!/usr/env python + +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration +# +# ---------------------------------------------------------------- +# Script : AtlRunQuerySave.py +# Project: AtlRunQuery +# Purpose: Utility to save pickled dictionary +# Authors: Andreas Hoecker (CERN), Joerg Stelzer (DESY) +# Created: Dec 2, 2008 +# ---------------------------------------------------------------- +# +# --------------------------------------------------------------------------------------------------- +# Creation of Pickled dictionary for output +# --------------------------------------------------------------------------------------------------- +from __future__ import with_statement +from CoolRunQuery.utils.AtlRunQueryTimer import timer +from CoolRunQuery.AtlRunQueryQueryConfig import QC +from CoolRunQuery.selector.AtlRunQuerySelectorBase import DataKey + +import pickle,sys,os +from CoolRunQuery.AtlRunQueryRun import Run + +def CreateResultDict( runlist ): + + if len(runlist)==0: return {}, {} + + # output directory + if not 'data' in os.listdir('.'): + os.mkdir('data') + + # define the header + header = [] + excludelist = [] + if Run.showrunnr: header += [DataKey(x) for x in ['Run', 'Links', '#LB']] + if Run.showtime: header += [DataKey('Start and endtime')] + if Run.showduration: header += [DataKey('Duration')] + header += [k for k in Run.ShowOrder if k not in excludelist] + + AddUpEvents(runlist) + + SaveResultTxt(runlist, header) + + dic = CreateDic(runlist, header) + + dic_basic = CreateDicBasicTypes(dic) + + SaveTypelessPickleResult(dic_basic) + + summary = CreateSummary(dic) + + return dic, summary + + +def AddUpEvents(runlist): + # Sum up total event numbers + for run in runlist: + try: + Run.totevents[0] += int(run.result["#Events"]) + except: + Run.totevents[1] += 1 + + + +def SaveResultTxt(runlist, header): + # write header to text file + f = open( '%s/QueryResult.txt' % QC.datapath, 'w' ) + print >> f, "data keys:", ', '.join([h.ResultKey for h in header]) + print >> f, 'number of runs: %i' % len(runlist) + + # now get the values for each run and write to file + for r in runlist: + line = [] + if Run.showrunnr: line += ["%i" % r.runNr, "", "%i" % r.lastlb] + if Run.showtime: line += ["%s" % r.timestr('seconds')] + if Run.showduration: line += ["%s" % r.durationstr()] + for k in Run.ShowOrder: line += [r.data[k.ResultKey]] + for head,item in zip(header,line): + if isinstance(item,tuple): item = '|'.join([str(x) for x in item]) + print >> f, '%40s: %s' % (head.ResultKey, item) + print >> f, '\n' + f.close() + + + + +def SaveTypelessPickleResult(pdic, filename = 'atlrunquery.pickle'): + # write pickle output + pf = open( '%s/atlrunquery.pickle' % QC.datapath, 'w' ) + try: pickle.dump(pdic, pf) + except Exception, e: + print 'ERROR: could not pickle results dictionary: "%r"' % e + sys.exit(1) + pf.close() + + + +def CreateDic(runlist, header): + # create keys for pickle dictionary + dic = {} + for r in runlist: + for k in header: + if k == 'Run': scontent = r.runNr + elif k == 'Links': scontent = "" + elif k == '#LB': scontent = (r.lastlb, [(lbtime[0]-r.lbtimes[0][0])*1e-9 for lbtime in r.lbtimes] + [ (r.lbtimes[-1][1]-r.lbtimes[0][0])*1e-9 ] ) + elif k == 'Start and endtime': scontent = r.timestr('seconds') + elif k == 'Duration': scontent = r.durationstr() + else: scontent = r.data[k.ResultKey] + dic.setdefault(k,[]).append(scontent) + return dic + + +def basic(v): + if hasattr(v,'pickled'): return v.pickled() + return v + +def CreateDicBasicTypes(dic): + dic_basic = {'Run':dic[DataKey('Run')]} + + for i,r in enumerate(dic_basic['Run']): + dic_basic[r] = dict([ ( k.pickled(), basic(v[i]) ) for k,v in dic.items()]) + + return dic_basic + + + + + +def CreateSummary(dic): + # create summary + dicsum = {} + for key, results in dic.items(): + if key.ResultKey in ['SMK','HLT PSK','L1 PSK','TorCurrent','SolCurrent','BGS Key','#LB']: continue + if key.Type==DataKey.DETECTOR: continue + for r in results: + if key=='Run': + dicsum.setdefault(key,0) + dicsum[key] += 1 + elif key.Type == DataKey.STREAM: + entry = r[0] + if entry == None or entry.value == 'n.a.': continue + dicsum.setdefault(key,[0,0]) + dicsum[key][0] += entry.value[0] + dicsum[key][1] += entry.value[1] + else: + try: + ir = int(r) + if not key in dicsum: dicsum[key] = 0 + dicsum[key] += ir + except: + pass + return dicsum diff --git a/Database/CoolRunQuery/python/output/AtlRunQueryXML.py b/Database/CoolRunQuery/python/output/AtlRunQueryXML.py new file mode 100644 index 00000000000..390a4e24d6f --- /dev/null +++ b/Database/CoolRunQuery/python/output/AtlRunQueryXML.py @@ -0,0 +1,277 @@ +#!/usr/env python + +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration +# +# ---------------------------------------------------------------- +# Script : AtlRunQueryXML.py +# Project: AtlRunQuery +# Purpose: XML file output of good run lists +# Authors: Andreas Hoecker (CERN), Joerg Stelzer (DESY) +# Created: May 6, 2009 +# ---------------------------------------------------------------- +# +# --------------------------------------------------------------------------------------------------- +# XML making +# --------------------------------------------------------------------------------------------------- + +from __future__ import with_statement +from CoolRunQuery.utils.AtlRunQueryTimer import timer + +import datetime, sys, os +from xml.dom.minidom import Document, DocumentType, Comment, Text, Element +from CoolRunQuery.AtlRunQueryRun import Run +from CoolRunQuery.utils.AtlRunQueryUtils import prettyNumber + +# --------------------------------------------------------------------------------------------------- +# Creation of XML output file +# --------------------------------------------------------------------------------------------------- + +# This is an Element, but does not print sub-elements. It is designed +# to hold attributes and CharacterData, and prints everything in one +# line (unlike Element.writexml(), which always adds a 'newline' +class TextElement(Element): + def __init__(self,name,data,doc): + Element.__init__(self,name) + if not isinstance(data, str): + raise TypeError, "node contents must be a string" + self.tagName = name + self.data = data + self.ownerDocument = doc + self.attOrder = [] + + def setAttribute(self, attname, value): + if not attname in self.attOrder: + self.attOrder += [attname] + Element.setAttribute(self, attname, value) + + def writexml(self, writer, indent="", addindent="", newl=""): + attrs = self._get_attributes() + atstr = '' + for a_name in self.attOrder: + atstr += ' %s="%s"' % (a_name, attrs[a_name].value) + + if self.data: + writer.write("%s<%s%s>%s</%s>%s" % (indent, self.tagName, atstr, self.data, self.tagName, newl ) ) + else: + writer.write("%s<%s%s/>%s" % (indent, self.tagName, atstr, newl ) ) + +def CreateRunStreamAndNeventsList( runlist ): + + # open SFO DB connection + from CoolRunQuery.utils.AtlRunQueryUtils import coolDbConn + from CoolRunQuery.AtlRunQuerySFO import GetSFO_NeventsAllPhysics + + cursor = coolDbConn.GetSFODBConnection().cursor() + cursor.arraysize=1000 + + # find streams + runnrlist = [r.runNr for r in runlist] + runstreamevents = GetSFO_NeventsAllPhysics( cursor, runnrlist ) # { runnr: { stream: [(LUMIBLOCKNR, NREVENTS)] } } + return runstreamevents, ",".join(['%i' % r for r in runnrlist]) + +def CreateXMLFile( runlist, options, origQuery, datapath, xmlfname, xmllabel, svnversion='Unknown' ): + + # show number of events per stream per run ? + ShowNumberOfEventsPerStreamPerRun = False + ShowNumberOfEventsPerStreamSummary = True + + + # find streams + runstreamevents, runnrliststr = CreateRunStreamAndNeventsList( runlist ) # { runnr: { stream: [(LUMIBLOCKNR, NREVENTS)] } } + + + + + #################################### + + ## create XML file of GRL + + #################################### + + doc = Document() + + docType = DocumentType('LumiRangeCollection') + docType.systemId = 'http://atlas-runquery.cern.ch/LumiRangeCollection.dtd' + doc.appendChild(docType) + + # number of comments + txt = ' Good-runs-list created by %s on %s ' % (sys.argv[0].split('/')[-1], str(datetime.datetime.now())) + doc.appendChild( doc.createComment( txt ) ) + + # root element + lrc = doc.createElement('LumiRangeCollection') + doc.appendChild(lrc) + + # NamedLumiRange + namedLR = doc.createElement('NamedLumiRange') + lrc.appendChild(namedLR) + + # name of NamedLumiRange + namedLR.appendChild(TextElement('Name',xmllabel,doc)) + + # version of NamedLumiRange + namedLR.appendChild(TextElement('Version','2.1',doc)) + + # metadata of NamedLumiRange + metadata = { + 'Query' : origQuery.split('/')[0], + 'RQTSVNVersion' : svnversion, + 'RunList' : runnrliststr + } + + for md in metadata: + mdelm = TextElement('Metadata', metadata[md], doc) + mdelm.setAttribute('Name', md) + namedLR.appendChild(mdelm) + + if ShowNumberOfEventsPerStreamSummary: + strsummdelm = doc.createElement('Metadata') + strsummdelm.setAttribute('Name','StreamListInfo') + namedLR.appendChild(strsummdelm) + + + # lumiblock collections of NamedLumiRange + streams_sum = {} + streams_byrun = {} + for run in runlist: + lbc = doc.createElement('LumiBlockCollection') + # run number + runnrelm = TextElement('Run',str(run.runNr),doc) + (rd0,rd1) = run.stats['SMK']['random'] + # protect against missing information + if rd0 == 'n.a.': rd0 = 0 + if rd1 == 'n.a.': rd1 = 0 + runnrelm.setAttribute('PrescaleRD0',0x1<<(3+rd0)) + runnrelm.setAttribute('PrescaleRD1',0x1<<(3+rd1)) + lbc.appendChild(runnrelm) + + # streams (initialisation) + streams = {} + streams_byrun[run.runNr] = streams + if runstreamevents.has_key(run.runNr): # protection in case the run does not have any stream + for stream in runstreamevents[run.runNr].keys(): + if 'physics_' == stream[:8]: + streams[stream] = [0,0] # only for physics streams + if not streams_sum.has_key(stream): streams_sum[stream] = [0,0] + # total number of events in stream + for (nlb,nev) in runstreamevents[run.runNr][stream]: streams[stream][0] += nev + + # lumiblock ranges + + for lbrange in run.data.getLBRanges(activeOnly=True): + lbrelm = TextElement('LBRange','',doc) + lbrelm.setAttribute('Start',lbrange[1]) + lbrelm.setAttribute('End',lbrange[2]-1) + lbc.appendChild(lbrelm) + # count nevents in streams + if runstreamevents.has_key(run.runNr): # protection in case the run does not have any stream + for stream, lbnevts in runstreamevents[run.runNr].items(): + if 'physics_' == stream[:8]: + for (nlb,nev) in lbnevts: + if nlb>=lbrange[1] and nlb<lbrange[2]: streams[stream][1] += nev + + # append stream element + s = streams.keys() + s.sort() + strselm = doc.createElement('StreamsInRun') + + for stream in s: + nevts = streams[stream] + if ShowNumberOfEventsPerStreamPerRun: + strelm = TextElement('Stream','',doc) + strelm.setAttribute('Name', stream) + strelm.setAttribute('TotalNumOfEvents', nevts[0]) + strelm.setAttribute('NumOfSelectedEvents', nevts[1]) + strselm.appendChild(strelm) + eff = 0 + if nevts[0] > 0: eff = nevts[1]/float(nevts[0])*100.0 + + # collect total number of events + streams_sum[stream][0] += nevts[0] + streams_sum[stream][1] += nevts[1] + + # append streams + if ShowNumberOfEventsPerStreamPerRun: lbc.appendChild(strselm) + + # append LumiBlickCollection + namedLR.appendChild(lbc) + + for stream in sorted(streams_sum.keys()): + nevts = streams_sum[stream] + if ShowNumberOfEventsPerStreamSummary: + strelm = TextElement('Stream','',doc) + strelm.setAttribute('Name', stream) + strelm.setAttribute('TotalNumOfEvents', nevts[0]) + strelm.setAttribute('NumOfSelectedEvents', nevts[1]) + strsummdelm.appendChild(strelm) + + + filename = '%s/%s' % (datapath, xmlfname) + #print "Writing",filename + xmlfile = open( filename, mode="w" ) + xmlfile.write( doc.toprettyxml(' ') ) + xmlfile.close() + + + + + #################################### + + ## create HTML + + #################################### + + + # provide also pretty html text output + htmltext = '' + hspace = ' ' + htmltext += '<table style="width: auto; border: 0px solid; border-width: margin: 0 0 0 0; 0px; border-spacing: 0px; border-collapse: separate; padding: 0px;" font-family: sans-serif; font-size: 85%%">\n' + htmltext += '<tr><td colspan="2"><b><font color="#999999">' + txt.strip() + '</font></b></td></tr>\n' + htmltext += '<tr><td style="vertical-align:top">SVN Version: </td><td> ' + svnversion + '</td></tr>\n' + htmltext += '<tr><td style="vertical-align:top">Query string:</td><td><b>' + origQuery.split('/')[0] + '</b></td></tr>\n' + htmltext += '<tr><td style="vertical-align:top">Run list:</td><td>' + runnrliststr + '</td></tr>\n' + htmltext += '</table>' + htmltext += '<hr color="#000000" size=1><font color="#777777">\n' + htmltext += '<table style="width: auto; border: 0px solid; border-width: margin: 0 0 0 0; 0px; border-spacing: 0px; border-collapse: separate; padding: 0px;" font-family: sans-serif; font-size: 90%%">\n' + + + # lumiblock collections of NamedLumiRange + for run in runlist: + # run number + htmltext += '<tr><td style="text-align:left;height:25px;vertical-align:bottom">Run <b>%i</b>:</td><td></td></tr>\n' % run.runNr + + # lumiblock ranges + + for lbrange in run.data.getLBRanges(activeOnly=True): + htmltext += '<tr><td></td><td style="text-align:left">LB range: [%5i-%5i]</td></tr>\n' % (lbrange[1],lbrange[2]-1) + + # append stream element + htmltext += '<tr><td></td><td style="text-align:left"></td></tr>' + htmltext += '<tr><td></td><td style="text-align:left"><table style="width: auto; border: 0px solid; border-width: margin: 0 0 0 0; 0px; border-spacing: 0px; border-collapse: separate; padding: 0px;font-family: sans-serif; font-size: 90%"><tr><td>Stream name</td><td>#Events total</td><td> #Events selected</td><td> Sel. fraction (%)</td></tr>\n' + + streams = streams_byrun[run.runNr] + for stream in sorted(streams.keys()): + nevts = streams[stream] + eff = (nevts[1]/float(nevts[0])*100.0) if (nevts[0] > 0) else 0 + htmltext += '<tr><td style="text-align:left"><i>%s</i></td><td style="text-align:right">%s</td><td style="text-align:right">%s</td><td style="text-align:right">%.4g</td></tr>\n' % (stream, prettyNumber(nevts[0]),prettyNumber(nevts[1]),eff) + + htmltext += '</table></td></tr>\n' + + + # append stream element + htmltext += '</table>' + htmltext += '<hr color="#000000" size=1><font color="#777777">\n' + htmltext += '<b>Stream summary for all selected runs:</b><br>\n' + htmltext += '<table style="width: auto; border: 0px solid; border-width: margin: 0 0 0 0; 0px; border-spacing: 0px; border-collapse: separate; padding: 0px;font-family: sans-serif; font-size: 95%"><tr><td>Stream name</td><td>#Events total</td><td> #Events selected</td><td> Sel. fraction (%)</td></tr>\n' + for stream in sorted(streams_sum.keys()): + nevts = streams_sum[stream] + eff = 0 + if nevts[0] > 0: eff = nevts[1]/float(nevts[0])*100.0 + htmltext += '<tr><td style="text-align:left"><i>%s</i></td><td style="text-align:right">%s</td><td style="text-align:right">%s</td><td style="text-align:right">%.4g</td></tr>\n' % (stream, prettyNumber(nevts[0]),prettyNumber(nevts[1]),eff) + htmltext += '</table>\n' + + + + + # provide also text output + return htmltext diff --git a/Database/CoolRunQuery/python/output/__init__.py b/Database/CoolRunQuery/python/output/__init__.py new file mode 100644 index 00000000000..74583d364ec --- /dev/null +++ b/Database/CoolRunQuery/python/output/__init__.py @@ -0,0 +1,2 @@ +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + diff --git a/Database/CoolRunQuery/python/selector/AtlRunQuerySelectorBase.py b/Database/CoolRunQuery/python/selector/AtlRunQuerySelectorBase.py new file mode 100644 index 00000000000..bd841eb6461 --- /dev/null +++ b/Database/CoolRunQuery/python/selector/AtlRunQuerySelectorBase.py @@ -0,0 +1,609 @@ +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + + +from PyCool import cool +from copy import deepcopy + +from CoolRunQuery.utils.AtlRunQueryUtils import coolDbConn, MergeRanges, SmartRangeCalulator #, runsOnServer, GetRanges +from CoolRunQuery.utils.AtlRunQueryIOV import IOVTime, IOVRange + +import sys +from time import time,strftime,gmtime +from collections import namedtuple, defaultdict + +from itertools import groupby +from operator import itemgetter + + +O = namedtuple('O', 'channel payload iovrange isvf') +def coolgen(coolobjs): + while coolobjs.goToNext(): + obj=coolobjs.currentRef() + yield O(obj.channelId(),obj.payloadValue, IOVRange(obj), False) + +ChDesc = namedtuple('ChDesc','CoolPayloadKey,CoolChannel,ResultKey,Header,SelectShowRetrieve') + +class DataKey(object): + SELECT = 0x1 + SHOW = 0x2 + RETRIEVE = 0x4 + + UNDEFINED = 0 + STREAM = 1 + DETECTOR = 2 + DQDEFECTS = 3 + + def __init__(self, x, keytype=UNDEFINED, selecttype = SELECT): + if isinstance(x,str): + self._cool_channel = 0 + self._cool_attr = '' + self._internal_key = x.strip() + self._second_internal_key = '' + self._table_header = x.strip() + else: + self._cool_channel = x[0] + self._cool_attr = x[2] + self._internal_key = x[1].strip() + self._second_internal_key = '' + self._table_header = x[1] if len(x)<4 else x[3] + self._type = keytype + self._select_show_retrieve = selecttype # 0-retrieve only, 1-select, 2-show, 3-select&show + + @property + def _seltype(self): + if self._select_show_retrieve == DataKey.SELECT: return "SELECT" + if self._select_show_retrieve == DataKey.SHOW: return "SHOW" + if self._select_show_retrieve == DataKey.RETRIEVE: return "RETRIEVE" + return "" + + def __repr__(self): + return "<DataKey '%s' type %i %s>" % (self._internal_key, self._type, self._seltype) + + def __eq__(self,other): + if isinstance(other,DataKey): + #print "Compare (%s) with (%s) ==> %r" % (self._internal_key,other._internal_key, self._internal_key==other._internal_key) + eq = self._internal_key==other._internal_key and self._second_internal_key==other._second_internal_key + return eq + else: + #print "Compare (%s) with '%s' ==> %r" % (self._internal_key, other, self._internal_key==other) + eq = self._internal_key==other + return eq + + def __cmp__(self,other): + if isinstance(other,DataKey): + if self._internal_key==other._internal_key and self._second_internal_key==other._second_internal_key: + return 0 + if self._internal_key==other._internal_key: + return self._second_internal_key.__cmp__(other._second_internal_key) + return self._internal_key.__cmp__(other._internal_key) + else: + #print "Compare (%s) with '%s' ==> %r" % (self._internal_key, other, self._internal_key==other) + return self._internal_key==other + + def __hash__(self): + return hash( (self._internal_key,self._second_internal_key) ) + + def pickled(self): + if self._second_internal_key != '': + return (self._internal_key,self._second_internal_key) + return self._internal_key + + + @property + def CoolPayloadKey(self): + return self._cool_attr + + @property + def CoolChannel(self): + return self._cool_channel + + @property + def ResultKey(self): + return self._internal_key + + @property + def Header(self): + return self._table_header + + @property + def SelectShowRetrieve(self): + return self._select_show_retrieve + + @property + def Type(self): + return self._type + + + + +class Selector(object): + db = "COMP200" + condtag = "" + def __init__(self, name): + self.name = name + self.priority = 50 + self.verbose = False + self.applySelection = True + self.mcenabled = False + def select(self, runlist): return runlist + def prettyValue(self, value, key): return value + def ApplySelection(self,key): return self.applySelection + def runAfterQuery(self,runlist): pass + + +class Condition(Selector): + def __init__(self, name, dbfolderkey, channelKeys): + super(Condition,self).__init__(name) + self.selDataMissing = False + self.setSchemaFolderTag(dbfolderkey) + self.setChannelKeys(channelKeys) + + def setSchemaFolderTag(self,dbfoldertag): + # setting schema, folder, and tag + self.schema, foldertag = dbfoldertag.split('::') + self.folder, self.tagname = (foldertag.split('#')+[''])[0:2] + if self.tagname=="": self.tagname = self.condtag + + + def setChannelKeys(self,channelKeys,ssr=None): + self.data_keys = [DataKey(x) for x in channelKeys] + if ssr: + assert len(ssr)==len(self.data_keys) + for (x,_ssr) in zip(self.data_keys,ssr): + x._select_show_retrieve = _ssr + + self._channelKeys = channelKeys + self._coolpayloadkey = [x[2] for x in channelKeys] + self._coolchannel = [x[0] for x in channelKeys] + self._resultKey = [x[1] for x in channelKeys] + self._headerKey = [x[1] for x in channelKeys] + if not ssr: + self._doSelectShowRetrieve = len(channelKeys)*[1] # 0-retrieve only, 1-select, 2-show, 3-select&show + else: + self._doSelectShowRetrieve = ssr + self._channeldesc = self.data_keys + + def _getFolder(self): + f = coolDbConn.GetDBConn(schema = self.schema,db=self.db).getFolder(self.folder) + if f.versioningMode()==0: self.tagname="" + if self.tagname not in ["HEAD", ""]: + self.tagname = f.resolveTag(self.tagname) + return f + + def addChannelKey(self,channelKey,ssr=None): + """ + channelKey: (0,'SMK','MasterConfigurationKey') + ssr: 0-retrieve only, 1-select, 2-show, 3-select&show + """ + + if DataKey(channelKey) in self.data_keys: + # key already exists + return + + self.data_keys += [ DataKey(channelKey) ] + if ssr: + self.data_keys[-1]._select_show_retrieve = ssr + + self._channelKeys += [ channelKey ] + self._coolpayloadkey += [ channelKey[2] ] + self._coolchannel += [ channelKey[0] ] + self._resultKey += [ channelKey[1] ] + self._headerKey += [ channelKey[1] ] + if ssr: + self._doSelectShowRetrieve += [ ssr ] + else: + self._doSelectShowRetrieve += [ 1 ] + self._channeldesc = self.data_keys + + + + def ResultKey(self): + return self._resultKey + + def ChannelKeys(self): + return self._channelKeys + + def CoolChannels(self): + return self._coolchannel + + def HeaderKeys(self): + return self._headerKey + + def DoSelectShowRetrieve(self): + return self._doSelectShowRetrieve + + def ChannelDesc(self): + return self._channeldesc + + def setShowOutput(self, listofchans=None ): + for cd in self.ChannelDesc(): + if listofchans and not cd.ResultKey in listofchans: continue + cd._select_show_retrieve |= 2 + self._doSelectShowRetrieve = [cd.SelectShowRetrieve for cd in self._channeldesc] + self.updateShowOrder() + + def updateShowOrder(self): + from CoolRunQuery.AtlRunQueryRun import Run + for cd in self.ChannelDesc(): + if cd.SelectShowRetrieve<2: continue + Run.AddToShowOrder(cd) + + +class RunLBBasedCondition(Condition): + def __init__(self, name, dbfolderkey, channelKeys=None): + super(RunLBBasedCondition,self).__init__(name, dbfolderkey, channelKeys) + + def _retrieve(self, iovmin, iovmax, f, sortedRanges): + chansel = None + for ch1,ch2 in sortedRanges: + if chansel==None: chansel = cool.ChannelSelection(ch1,ch2,cool.ChannelSelection.sinceBeforeChannel) + else: chansel.addRange(ch1,ch2) + print self.name,"browsing objects with tag",self.tagname + return coolgen(f.browseObjects( iovmin, iovmax, chansel, self.tagname)) + + def findPayload(self, runNr, iovpllist): + """ + iovpllist is a list [(IOV,payload),(IOV,payload),..,(IOV,payload)] + """ + for x in iovpllist: + if IOVTime(runNr,1).inRange(x[0]): return x[1] + return 'n.a.' + + def getSortedAndCompletedPayloadDict(self, iovpllist, runlist, key): + # reduce to data in the run 'runNr' and sort by iov start time + + pld = defaultdict(list) + if not iovpllist: return pld + if not runlist: return pld + + runnrlist = [r.runNr for r in runlist] + nlb = dict([(r.runNr,r.lastlb) for r in runlist]) + + # first and last run of the runlist + firstrun = runnrlist[0] + lastrun = runnrlist[-1] + + for iov, pl in iovpllist: + first = max(firstrun,iov.startTime.run) + last = min(lastrun,(iov.endTime-1).run) + for r in xrange(first,last+1): + if r in runnrlist: + pld[r].append((deepcopy(iov),pl)) + + # adjustments of IOV + for runnr, iovplbyrun in pld.items(): + # adjust lb 0 to lb 1 + if iovplbyrun[0][0].startTime.lb==0: iovplbyrun[0][0].startTime.lb=1 + + # truncate first IOV to a single run + iovplbyrun[0][0].truncateToSingleRun(runnr) + + # sometimes and IOV has [RunLB1 - RunLB2), where RunLB2 has LB==1 + # -> that gets truncated to [RunLB2 - RunLB2), which is obviously not valid + # -> so we slice that right out + if iovplbyrun[0][0].startTime == iovplbyrun[0][0].endTime: + iovplbyrun[:1] = [] + + # truncate last IOV to a single run + lastiov = iovplbyrun[-1][0] + lastiov.truncateToSingleRun(runnr) + + # insert IOVs for missing ranges (value n.a.) + idx=0 + curTime = IOVTime(runnr,1) + while curTime<lastiov.startTime: + iov = iovplbyrun[idx][0] + if curTime < iov.startTime: + # insert n.a. for missing iov + missingIOV = IOVRange(starttime=curTime, endtime=iov.startTime) + iovplbyrun.insert( idx, (missingIOV, "n.a.") ) + curTime = IOVTime(iov.endTime) + idx += 1 + + # also check if IOV at the end is missing + runEndTime = IOVTime(runnr,nlb[runnr]+1) # last lb + 1 + if lastiov.endTime<runEndTime: + missingIOV = IOVRange(starttime=lastiov.endTime, endtime=runEndTime) + iovplbyrun.append( (missingIOV, "n.a.") ) + + return pld + + + + def readCondData(self, runranges, f, sortedRanges): + # get the data from cool + condData = defaultdict(list) + + keys = dict([(ch,set(k)) for ch, k in groupby(sorted(self.ChannelKeys(), key=itemgetter(0)), itemgetter(0))]) + + for rr in runranges: + iovmin=(rr[0] << 32)+0 + iovmax=((rr[1]+1) << 32)-1 + + objs = self._retrieve(iovmin, iovmax, f, sortedRanges) + + for obj in objs: + #if 'DQDEFECT' in keys: + # print '\n YYYYYYY ',obj + ch = obj.channel + for chtmp, internalKey, payloadKey in keys[ch]: + if type(payloadKey)==tuple: + if obj.isvf: payload = obj.payload + else: payload = tuple(map(obj.payload, payloadKey)) + else: + payload = obj.payload(payloadKey) + + #if 'DQDEFECT' in keys: + # print '\n YYYYYYY ', payload + + condData[internalKey].append( (IOVRange(obj.iovrange), payload) ) + return condData + + + + + def select(self, runlist): + print self, + sys.stdout.flush() + start = time() + newrunlist = [] + + sortedChannel = sorted( list( set( self.CoolChannels() ) ) ) + sortedRanges = MergeRanges([(x,x) for x in sortedChannel]) + + runranges = SmartRangeCalulator(runlist) + + f = self._getFolder() + + condData = self.readCondData(runranges, f, sortedRanges) + + # for each key sort the data by IOV start time + for k in self.ResultKey(): + condData[k].sort() + #if k.startswith('ofllumi'): + # for x in condData[k]: + # print k,x + + condDataDict = {} + runnrlist = [r.runNr for r in runlist] + for k in self.ResultKey(): + condDataDict[k] = self.getSortedAndCompletedPayloadDict(condData[k],runlist,k) + + for run in runlist: # go through old runlist and see + + rejectSomething = False + for k in self.ResultKey(): + if not run.runNr in condDataDict[k]: + if self.ApplySelection(k): + rejectSomething = True + run.addResult(k, "n.a.") + continue + + datavec = condDataDict[k][run.runNr] + + if not datavec or len(datavec)==0: + run.addResult(k, "n.a.") + continue + + if 'n.a.' in datavec: run.showDataIncomplete = True + + anyDataSelected = False + for iov, data in datavec: + #if k=="DQ": + # print "CCCCCCCCCCCC",k,data + self.selDataMissing = False + if self.ApplySelection(k) and not self.passes(data,k): + run.addResult(k, self.prettyValue(data,k), iov, reject=True) + rejectSomething = True + else: + run.addResult(k, self.prettyValue(data,k), iov) + if self.selDataMissing: run.selDataIncomplete = True + + if not (rejectSomething and self.rejectRun(run)): + newrunlist += [run.runNr] + + runlist = [r for r in runlist if r.runNr in newrunlist] + + duration = time() - start + + if self.applySelection: print " ==> %i runs found (%.2f sec)" % (len(runlist),duration) + else: print " ==> Done (%g sec)" % duration + + return runlist + + + def rejectRun(self,run): + return run.data.isRejected + + +class TimeBasedCondition(Condition): + def __init__(self, name, dbfolderkey, channelKeys=None): + super(TimeBasedCondition,self).__init__(name, dbfolderkey, channelKeys) + + + def getSortedAndCompletedPayloadDict(self, iovpllist, runlist,key): + # reduce to data in the run 'runNr' and sort by iov start time + + pld = {} + if not iovpllist or not runlist: return pld + + startiovindex = 0 + for run in runlist: + pld[run.runNr] = [] + + if startiovindex<-1: + continue + + # find the first iov that overlaps + iovindex = startiovindex + while True: + try: + iov = iovpllist[iovindex][0] + iovstart = iov.startTime.time + iovend = iov.endTime.time + + if iovend>run.sor: + startiovindex = iovindex + break + + if iovstart>run.eor: + startiovindex = -1 + break + + if iovend<run.sor and iovindex==len(iovpllist)-1: + startiovindex = -2 + break + + iovindex += 1 + except IndexError: + startiovindex = -1 + + if startiovindex<0: + continue + + # find the last iov that overlaps + iovindex = startiovindex + while True: + iov = iovpllist[iovindex][0] + iovstart = iov.startTime.time + iovend = iov.endTime.time + + if iovend>=run.eor or iovindex==len(iovpllist)-1: + endiovindex = iovindex + break + + iovindex += 1 + + # now we have the first and last iov that overlap with that run: startiovindex, endiovindex + lbindex = 0 + for iovindex in range(startiovindex, endiovindex+1): + iov,pl = iovpllist[iovindex] + iovstart = iov.startTime.time + iovend = iov.endTime.time + endrun = run.runNr + + while lbindex<len(run.lbtimes) and run.lbtimes[lbindex][0]<iovstart: + lbindex += 1 + startlb = lbindex+1 + + while lbindex<len(run.lbtimes) and run.lbtimes[lbindex][0]<iovend: + lbindex += 1 + lbindex -= 1 + + # now lbindex points to the last lb that starts within the iov or to one after the last lb + if lbindex==len(run.lbtimes)-1: + endlb = 0 + endrun += 1 + else: + endlb = lbindex+2 # +1 for lb-index->lb-number, +1 for closed interval + + # append info of this IOV + lastIOV,lastpl=None,None + if len(pld[run.runNr])>0: + lastIOV,lastpl = pld[run.runNr][-1] + if lastpl==pl: + lastIOV.endTime.run = endrun + lastIOV.endTime.lb = endlb + else: + pld[run.runNr] += [(IOVRange(runStart=run.runNr, lbStart=startlb, runEnd=endrun, lbEnd=endlb), pl)] + + + # for the next run we start looking at: + startiovindex = endiovindex + + return pld + + + def select(self, runlist): + print self, + sys.stdout.flush() + start = time() + newrunlist = [] + f = coolDbConn.GetDBConn(schema=self.schema, db=self.db).getFolder(self.folder) + + sortedChannel = sorted( list( set( self.CoolChannels() ) ) ) + chansel = None + for ch in sortedChannel: + if chansel==None: chansel = cool.ChannelSelection(ch,ch,cool.ChannelSelection.sinceBeforeChannel) + else: chansel.addChannel(ch) + + runranges = SmartRangeCalulator(runlist,True) + condData = defaultdict(list) + + + + for rr in runranges: + firstrun = runlist[runlist.index(rr[0])] + lastrun = runlist[runlist.index(rr[1])] + iovmin=firstrun.sor + iovmax=lastrun.eor + + objs = f.browseObjects( iovmin, iovmax, chansel) + while objs.goToNext(): + obj= objs.currentRef() + ch = obj.channelId() + for chKey in self.ChannelKeys(): + (channelNumber, resultKey, payloadKey) = chKey + if channelNumber != ch: continue + isBlob = (resultKey == 'olc:bcidmask') + + if isBlob: + payloadvalue = obj.payload()[chKey[2]].read() + else: + payloadvalue = obj.payloadValue(chKey[2]) + + condData[resultKey].append( (IOVRange(obj=obj, timebased=True), payloadvalue) ) + + + # for each key sort the data by IOV start time + for k in self.ResultKey(): condData[k].sort() + + condDataDict = {} + for k in self.ResultKey(): + condDataDict[k] = self.getSortedAndCompletedPayloadDict(condData[k],runlist,k) + + for run in runlist: + + rejectSomething = False + for k in self.ResultKey(): + + if not run.runNr in condDataDict[k]: + run.addResult(k, "n.a.") + continue + + datavec = condDataDict[k][run.runNr] + + if not datavec or len(datavec)==0: + run.addResult(k, "n.a.") + continue + + if 'n.a.' in datavec: run.showDataIncomplete=True + + anyDataSelected = False + for iov, data in datavec: + self.selDataMissing = False + if self.ApplySelection(k) and not self.passes(data,k): + run.addResult(k, self.prettyValue(data,k), iov, reject=True) + rejectSomething = True + continue + + run.addResult(k, self.prettyValue(data,k), iov) + + if self.selDataMissing: run.selDataIncomplete = True + + if not (rejectSomething and self.rejectRun(run)): + newrunlist += [run.runNr] + + + runlist = [r for r in runlist if r.runNr in newrunlist] + + duration = time() - start + + if self.applySelection: print " ==> %i runs found (%.2f sec)" % (len(runlist),duration) + else: print " ==> Done (%g sec)" % duration + + return runlist + + def rejectRun(self,run): + return run.data.isRejected + diff --git a/Database/CoolRunQuery/python/selector/AtlRunQuerySelectorDQ.py b/Database/CoolRunQuery/python/selector/AtlRunQuerySelectorDQ.py new file mode 100644 index 00000000000..82fdba08ce2 --- /dev/null +++ b/Database/CoolRunQuery/python/selector/AtlRunQuerySelectorDQ.py @@ -0,0 +1,697 @@ +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + + +from .AtlRunQuerySelectorBase import Selector, RunLBBasedCondition, O +from CoolRunQuery.utils.AtlRunQueryIOV import IOVRange +from CoolRunQuery.utils.AtlRunQueryUtils import coolDbConn +from CoolRunQuery.utils.AtlRunQueryLookup import DQChannel + +from collections import defaultdict, namedtuple + +DQDefectPayload = namedtuple("DQDefectPayload", "defect comment user ignore primary tolerable recoverable") +DQDefectPayload.pickled = DQDefectPayload._asdict + +DD = namedtuple("DD","description comment since until") + +def vfgen(vfobjs): + for obj in vfobjs: + yield O(obj.channel, (str(obj.Code),obj.Comment), IOVRange(starttime=obj.since, endtime=obj.until), True) + +class DQSelector(Selector): + def __init__(self, name='dataquality'): + super(DQSelector,self).__init__(name) + self.selectors = {} + + from CoolRunQuery.AtlRunQuerySelectorWorker import SelectorWorker + SelectorWorker.getRetrieveSelector('readyforphysics','ReadyForPhysicsSelector') + + + def __getChannelAndFolder(self, chanAndfolder): + cf = chanAndfolder.split() + if len(cf)==1: + channel='' + folder=cf[0] + else: + channel, folder = cf + return (channel,folder) + + def __getCondition(self, folder): + if folder in self.selectors: + return self.selectors[folder] + if folder.startswith('DEFECTS'): + self.selectors[folder] = DQDefectCondition(self.name+"DEFECTS", folder) + else: + self.selectors[folder] = DQCondition(self.name+folder, folder, []) + return self.selectors[folder] + + def addSelectionChannel(self, dq=[]): + """ + dq can be e.g. ['EMBA yellow SHIFTOFL', 'FCALA green+ DQMFOFL', 'EMBA,EMBC,EMECA,EMECC yellow+ SHIFTOFL'] + this example should be translated into two selectors, one for each of the folders 'SHIFTOFL' and 'DQMFOFL' + """ + folderCriteria = defaultdict(list) + for x in dq: + s = x.split() + folderCriteria[s[-1]].append(s[:-1]) + for f in folderCriteria.keys(): + if f.startswith('DEFECTS'): + self.selectors[f] = DQDefectCondition(self.name+"DEFECTS", f ) + self.selectors[f].addSelectionChannels(folderCriteria[f]) + else: + self.selectors[f] = DQCondition(self.name+f, f, folderCriteria[f]) + + + def addShowChannel(self, chanAndfolder): + (channel,folder) = self.__getChannelAndFolder(chanAndfolder) + condition = self.__getCondition(folder) + condition.addShowChannel(folder, channel, 'HEAD') + + def setShowOutput(self): + for sel in self.selectors.values(): + sel.setShowOutput() + + + def __str__(self): + s = "" + for sel in self.selectors.values(): + s += "\n%s" % sel + return s + + + def select(self, runlist): + # garbage collector + # import gc + # gcod = gc.get_objects() + # print "GC objects",len(gcod) + # print "GC object count",gc.get_count() + + for sel in self.selectors.values(): runlist = sel.select(runlist) + return runlist + + def runAfterQuery(self,runlist): + for sel in self.selectors.values(): sel.runAfterQuery(runlist) + + + +class DQCondition(RunLBBasedCondition): + + code = {'unknown': 0, 'red': 1, 'yellow': 2, 'green': 3, 'black': -1} + color = {'n.a.': 'n.a.', '0': 'U', '1': 'R', '2': 'Y', '3': 'G', '-1': 'B'} + invert = {'n.a.': -2, 'B': -1, 'U': 0, 'R': 1, 'Y': 2, 'G': 3 } + + def __init__(self, name, foldername, dq): + + self.foldername = foldername + self.channelNames = [] + self.channelNamesFlat = [] + self.channels = [] + self.channelsflat = [] + self.flags = [] + self.useVirtualFlags = False + + dbname = 'COOLOFL_GLOBAL' + if 'ONL' in self.foldername: dbname = 'COOLONL_GLOBAL' + + # set up virtual flag logic folder and expand wild cards for VF in dq + self.GetVirtualFlagsExpanded(dq, Selector.db, dbname) + + for x in dq: + ch = x[0].split(',') + self.channelNames += [["%s:%s" % (self.foldername,chn) for chn in ch]] + self.channels += [[self.DQChannel(chn) for chn in ch]] + self.flags += [(x+[''])[1]] + + for chN in self.channelNames: self.channelNamesFlat += chN + for ch in self.channels: self.channelsflat += ch + + self.channelCommentsFlat = [n+"_m" for n in self.channelNamesFlat] + + # choose DB based on foldername + + # out of 'SHIFTOFL', 'DQCALCOFL', 'DQMFOFL', 'DQMFOFLH', 'DCSOFL', 'TISUMM', 'LBSUMM', 'MUONCALIB', 'DQMFONL', 'SHIFTONL' + # these ones have a comment field + + self.folderHasComment = ( (self.foldername == "SHIFTOFL") or (self.foldername.startswith("SHIFTOFL#")) or + (self.foldername == "LBSUMM") or (self.foldername.startswith("LBSUMM#")) or + (self.foldername == "DQCALCOFL") or (self.foldername.startswith("DQCALCOFL#")) or + (self.foldername.startswith("MUONCALIB#")) ) + + # channel keys: + # [Code (Int32) : 1], [deadFrac (Float) : 0], [Thrust (Float) : 0], [Comment (String255) : "warm start"] + if self.folderHasComment: + channelKeys = zip(self.channelsflat, self.channelNamesFlat, len(self.channelsflat)*[('Code','Comment')]) + else: + channelKeys = zip(self.channelsflat, self.channelNamesFlat, len(self.channelsflat)*['Code']) + + super(DQCondition,self).__init__(name=name, + dbfolderkey='%s::/GLOBAL/DETSTATUS/%s' % (dbname, self.foldername), + channelKeys = channelKeys) + + self.flagInterpreter = {} + for flag in self.flags: + self.flagInterpreter[flag] = {} + d = self.flagInterpreter[flag] + d['refVal'] = None + d['passFnc'] = None + d['passFncName'] = '' + + if flag=='n.a.': + d['passFnc'] = lambda x: x==-2 + d['passFncName'] = "x=='n.a.'" + elif flag[-1] in '+-': + cd = self.code[flag[:-1].lower()] + d['refVal'] = cd + if flag[-1]=='+': + if cd == -1: d['passFnc'] = lambda x: int(x)>=-1 + elif cd == 0:d['passFnc'] = lambda x: int(x)>=0 + elif cd == 1:d['passFnc'] = lambda x: int(x)>=1 + elif cd == 2:d['passFnc'] = lambda x: int(x)>=2 + elif cd == 3:d['passFnc'] = lambda x: int(x)>=3 + d['passFncName'] = "x>='%i'" % d['refVal'] + else: + if cd == -1: d['passFnc'] = lambda x: int(x)<=-1 + elif cd == 0:d['passFnc'] = lambda x: int(x)<=0 + elif cd == 1:d['passFnc'] = lambda x: int(x)<=1 + elif cd == 2:d['passFnc'] = lambda x: int(x)<=2 + elif cd == 3:d['passFnc'] = lambda x: int(x)<=3 + d['passFncName'] = "x<='%i'" % d['refVal'] + else: + cd = self.code[flag.lower()] + d['refVal'] = cd + if cd == -1: d['passFnc'] = lambda x: int(x)==-1 + elif cd == 0:d['passFnc'] = lambda x: int(x)==0 + elif cd == 1:d['passFnc'] = lambda x: int(x)==1 + elif cd == 2:d['passFnc'] = lambda x: int(x)==2 + elif cd == 3:d['passFnc'] = lambda x: int(x)==3 + d['passFncName'] = "x=='%i'" % d['refVal'] + + self.passSpecs = {} + for chgrp, flag in zip(self.channelNames,self.flags): + for ch in chgrp: + self.passSpecs[ch] = self.flagInterpreter[flag] + + def _getFolder(self): + from CoolRunQuery.AtlRunQueryRun import RunData + RunData.DQLogic = self.channelNames + RunData.DQKeys = self.channelNamesFlat + + f = coolDbConn.GetDBConn(schema = self.schema,db=self.db).getFolder(self.folder) + if self.useVirtualFlags: + f = self.VirtualFlagFolder(f) + else: + if f.versioningMode()==0: self.tagname="" + if self.tagname not in ["HEAD", ""]: + self.tagname = f.resolveTag(self.tagname) + return f + + def _retrieve(self, iovmin, iovmax, f, sortedRanges): + if self.useVirtualFlags: + channels = [x.lower().split(':')[-1] for x in self.channelNamesFlat] + objs = vfgen(f.browseObjects( iovmin, iovmax, channels, self.tagname )) + else: + objs = super(DQCondition,self)._retrieve(iovmin, iovmax, f, sortedRanges) + return objs + + def DQChannel(self,dqfullname): + dqname = dqfullname.split(':')[-1].split('#')[0] + if self.useVirtualFlags and dqname in self.vfl.get_logic_list().keys(): + try: + return self.vfl.get_logic_list()[dqname].record.channel + except: + return self.vfl.get_logic_list()[dqname].channel + return DQChannel(dqname) + + def GetVirtualFlagsExpanded(self, dqlist, db, schema): + for i in xrange(len(dqlist)): + dqs = dqlist[i][0].split(',') + newdqs = [] + for c in dqs: + if '_' in c: + newdqs += self.ExpandVFlag(c,db,schema) + else: + newdqs.append(c) + dqlist[i][0] = ','.join(newdqs) + + def ExpandVFlag(self, cpflag, db, schema): + vfl = self.GetVirtualFlagLogic(db, schema) + vflags = vfl.get_logic_list().keys() + + useprimaries = cpflag[-1]=='+' + cpflag=cpflag.rstrip('+') + + expflags = [] + + if cpflag in vflags: + self.AddVFHeaderData(cpflag) + expflags += [cpflag] + if useprimaries: expflags += self.getVFDef(cpflag) + else: + for vf in vflags: + if vf.startswith(cpflag): + self.AddVFHeaderData(vf) + expflags += [vf] + if useprimaries: expflags += self.getVFDef(vf) + + if len(expflags)==0: + raise RuntimeError("Virtual Flag pattern %s does not match any virtual flag: %r" % (cpflag, vfl.get_logic_list().keys())) + + return expflags + + + def GetVirtualFlagLogic(self, db, schema): + if self.useVirtualFlags: return self.vfl + try: # import + from VirtualFlags import VirtualFlagLogicFolder, VirtualFlagFolder + except ImportError: + print "Can't import virtual flags" + import traceback + traceback.print_exc() + + self.VirtualFlagFolder = VirtualFlagFolder + self.vfl = VirtualFlagLogicFolder(coolDbConn.GetDBConn(schema=schema, db=db)) + self.logics = self.vfl.get_logic_list() + self.useVirtualFlags = True + return self.vfl + + def AddVFHeaderData(self,cpflag): + from CoolRunQuery.AtlRunQueryRun import Run + if cpflag in Run.Fieldinfo: return + Run.Fieldinfo[cpflag] = '<strong><b>%s</b></strong><br><table width="300"><tr><td>%s</td></tr></div>' % \ + (self.vfl.get_logic_list()[cpflag].comment, + ", ".join(self.getVFDef(cpflag)) ) + + def getVFDef(self,cpflag): + return self.vfl.resolve_primary_flags(self.vfl.resolve_dependancies([cpflag])) + + + + def ApplySelection(self,key): + if key in self.passSpecs: return True + return False + + def addShowChannel(self, folder, channelname, tag): + tmplist = [[channelname,'']] + self.GetVirtualFlagsExpanded(tmplist, Selector.db, self.schema) + + for shch in tmplist[0][0].split(','): + self.addSingleShowChannel(folder+':'+shch) + + def addSingleShowChannel(self, channelname): + ch = self.DQChannel(channelname) + ssr = self.DoSelectShowRetrieve() + if ch in self.channelsflat: # channel exists already, just need to set showoutput to true + idx = self.channelsflat.index(ch) # location of the channel in the flat list + self.channelNamesFlat[idx] = channelname + ssr[idx] += 2 + else: + self.channelNamesFlat += [channelname] + self.channelsflat += [self.DQChannel(channelname)] + ssr += [2] + # re-assign + self.channels = self.channelsflat + + if self.folderHasComment: + channelKeys = zip(self.channelsflat, self.channelNamesFlat, len(self.channelsflat)*[('Code','Comment')]) + else: + channelKeys = zip(self.channelsflat, self.channelNamesFlat, len(self.channelsflat)*['Code']) + + self.setChannelKeys(channelKeys,ssr) + + def __str__(self): + if self.applySelection: + s = "SELOUT Checking in the DQ folder %s" % self.foldername + flagCh = defaultdict(list) + for flag,ch in zip(self.flags,self.channelNames): + flagCh[flag].append('(' + ' or '.join(ch) + ')') + for flag in flagCh: + chlist = ' and '.join(flagCh[flag]) + s += "\nSELOUT %s is %r" % (chlist, flag) + return s + else: + return "Retrieving DQ for channels %r from folder %s" % (self.channelNamesFlat, self.foldername) + + def passes(self,values, key): + passfnc = self.passSpecs[key]['passFnc'] + if isinstance(values,tuple): v = values[0] + else: v = values + if v=='n.a.': v=-2 + try: + if passfnc(v): # passed this DQ flag in the OR group? + return True + except ValueError: + # happens when comparing 'n.a.' with 0,1,2,3 (for 'U','R','Y','G') ... and the black flag? (AH) + # should never set pass=True + pass + + return False + + def rejectRun(self,run): + for k in self.ResultKey(): + passfnc = self.passSpecs[k]['passFnc'] + for entry in run.data[k]: + v = entry.value + dqres = v[0] if type(v)==tuple else v + dqcode = self.invert[dqres] + entry.rejected = not passfnc( dqcode ) + return super(DQCondition,self).rejectRun(run) + + + + def prettyValue(self, value, key): + if type(value)==tuple: + return (self.color[value[0]], value[1]) + else: + return self.color[value] + + def runAfterQuery(self,runlist): + dqs = ['G','Y','R','B','U','n.a.'] + for run in runlist: + for k in self.ResultKey(): + n={} + run.stats[k] = {} + for dq in dqs: n[dq] = 0 + blocks = [] + for entry in run.data[k]: + if self.folderHasComment: + if entry.value == 'n.a.': + dq, dqcomment = ('n.a.','') + else: + dq, dqcomment = entry.value + else: + dq, dqcomment = (entry.value,None) + + n[dq] += len(entry) + if entry.startlb == 0: n[dq] -= 1 # CAUTION: problem with LB-1 = 0 ==> needs to be corrected + if len(blocks) > 0 and blocks[-1][0]==(dq,dqcomment) and blocks[-1][2]==entry.startlb: + blocks[-1][2] = entry.endlb + else: + blocks += [ [(dq, dqcomment), entry.startlb, entry.endlb] ] + + maxn = max(n.values()) + # find maximum DQ flag + dqmax = dqs[0] + for dq in dqs: + if n[dq]==maxn: + dqmax=dq + run.result[k]=dqmax + run.stats[k] = { "counts" : n, "max": dqmax, "blocks": blocks } + + + + + + + + + + + + + + +class DQDefectCondition(RunLBBasedCondition): + + def __init__(self, name, folder): + self.foldername = folder # DEFECTS#<TAG> + self.primaries = {} # self.primaries[runnr] is the list of primary defects that are set in each run + self.channelNames = [] + self.selectionChannelNames = [] + self.global_ignore = set() # set of all primary defects that should be ignored in all defects + super(DQDefectCondition,self).__init__(name=name, + dbfolderkey = 'COOLOFL_GLOBAL::%s' % self.foldername, # folder name handled internally by DQDefects + channelKeys = [('DQDEFECT', 'DQ',('Code','Comment'))]) + + self.data_keys[0]._second_internal_key = self.tagname + + def _getFolder(self): + from CoolRunQuery.AtlRunQueryRun import RunData + RunData.DefectSelector = self._pass + self.__db = coolDbConn.GetDBConn('DEFECTS',self.tagname) + return self.__db # return the DB, not the folder + + def _pass(self,data): + these_defects = [] + if data!=None: + for x in data: + if x.value.ignore==None: # no ignore + these_defects += [x.value.defect] + else: + these_defects += [x.value.defect+'\\'+x.value.ignore] + for orGroup in self.selectionChannelNames: + any_passes = False + for k in orGroup: + if k[0]=='\\': continue # "\Defect" are modifiers not requirements so we ignore it in the check + if k[0]=='!': # !DEFECT + kk = k[1:] + passes = not kk in these_defects + else: + passes = k in these_defects + if passes: + any_passes = True + break + if not any_passes: # none pass in the OR-group + return False + return True + + def _fix_channel_names(self, db): + newl = [] + for litem in self.selectionChannelNames: + newlitem = [] + for k in litem: + if k[0]=='\\': + newlitem.append(k) + elif k[0] == '!': + newlitem.append('!' + db.normalize_defect_names(k[1:])) + else: + newlitem.append(db.normalize_defect_names(k)) + newl.append(newlitem) + self.selectionChannelNames = newl + self.global_ignore = set(db.normalize_defect_names(self.global_ignore)) + + + def __getChannels(self, db, channel_name_patterns): + """ based on the patterns in the list channel_name_patterns create a + list of pairs of patterns and matching defects ( pattern, set(matching defects) ) + and attach it to the global Run.FieldInfo dictionary under 'DQ' + """ + from re import compile + from CoolRunQuery.AtlRunQueryRun import Run + if not 'DQ' in Run.Fieldinfo: Run.Fieldinfo['DQ'] = {} + if not 'DefMatch' in Run.Fieldinfo['DQ']: Run.Fieldinfo['DQ']['DefMatch'] = [] + Run.Fieldinfo['DQ']['IntolerableDefects'] = db.get_intolerable_defects(old_primary_only=False) + matches = set() + for pattern in channel_name_patterns: + if pattern.upper() != 'DET': + cpattern = compile(pattern) + channelnames = set(filter(cpattern.match,db.defect_names)) + channelnames.update(filter(cpattern.match,db.virtual_defect_names)) + else: + channelnames = set([d for d in db.defect_names if not '_' in d]) + channelnames.update([d for d in db.virtual_defect_names if not '_' in d]) + matches.update(channelnames) + Run.Fieldinfo['DQ']['DefMatch'] += [(pattern,channelnames)] + return matches + + + def _retrieve(self, iovmin, iovmax, db, sortedRanges): + runmin = iovmin>>32 + runmax = iovmax>>32 + since = (runmin,iovmin&0xFFFFFFFF) + until = (runmax,iovmax&0xFFFFFFFF) + + # add the channels for showing (those can be wildcarded, so call __getChannels to get exact matches) + channels = self.__getChannels(db,self.channelNames) + + # Rewrite defect names to get cases to be correct + self._fix_channel_names(db) + + # add the channels for selection (those have to match exactly, they are simply added) + channels_with_ignore = {} # unique + for selChans in self.selectionChannelNames: + channels.update([x.lstrip('!') for x in selChans if not '\\' in x]) + for x in selChans: # for the defects with ignore condition + if not '\\' in x: continue + channel, ignore_str = self.sort_uniq_ignores(x).split('\\',1) + if not ignore_str in channels_with_ignore: channels_with_ignore[ignore_str] = [] + channels_with_ignore[ignore_str] += [channel.lstrip('!')] + + #print "CHANNELS",channels + if len(channels) + len(channels_with_ignore)==0: return [] + + # we need to remove the special case ANY from the set + if 'ANY' in channels: channels.remove('ANY') + + res = [] if len(channels)==0 else [ (db.retrieve(since=since, until=until, channels=channels, ignore=self.global_ignore, with_primary_dependencies=True).trim_iovs, None) ] + + # and also treat the defects with 'ignore' condition + for _ignore_str, _channels in channels_with_ignore.items(): + ignore = set(_ignore_str.split('\\')) + ignore.update(self.global_ignore) + ## https://svnweb.cern.ch/trac/atlasoff/browser/DataQuality/DQDefects/tags/DQDefects-00-00-24/python/db.py#L154 + res += [ (db.retrieve(since=since, until=until, channels=_channels, ignore=ignore, with_primary_dependencies=True).trim_iovs, _ignore_str) ] + + return self.defgen(db, res, channels, channels_with_ignore) + + + + def defgen(self, db, defects_with_ignore, channels, channels_with_ignore): + """ + defects: list of defects returned by query + channels: explicit list of channels that matched the pattern + """ + intolerableDefects = db.get_intolerable_defects(old_primary_only=False) + for defects, ignore in defects_with_ignore: + chanlist = channels if ignore==None else channels_with_ignore[ignore] + for d in defects: + if not d.present: continue + isVirtual = (d.user == 'sys:virtual') # db.defect_is_virtual(d.channel) + if not isVirtual: + run = d.since.run + # fill list of primaries defects for this run (this is needed for the comments, but not for the selection) + if not run in self.primaries: self.primaries[run] = [] + self.primaries[run] += [d] + if not d.channel in chanlist: continue + + defPayload = DQDefectPayload(defect = d.channel, comment = d.comment, + user = d.user, primary = not isVirtual, + ignore = ignore, + tolerable = (not d.channel in intolerableDefects), + recoverable = d.recoverable) + # note that the comment is either the user's comment, + # a comment that the defect is auto-generated, or the + # list of source defects in case of virtual defects + + #o = O("DQDEFECT", (d.channel, d.comment, ignore), IOVRange(starttime=d.since.real, endtime=d.until.real), True) + o = O("DQDEFECT", defPayload, IOVRange(starttime=d.since.real, endtime=d.until.real), True) + yield o + + + + def ApplySelection(self,key): + return key=='DQ' and len(self.selectionChannelNames)!=0 + + + def sort_uniq_ignores(self,requirement): + if not '\\' in requirement: return requirement + x = requirement.split('\\') + new_req = x[0] + '\\' + '\\'.join(sorted(list(set(x[1:])))) + return new_req + + + def addSelectionChannels(self, dq): + for channels,flag in dq: + a = [] + prefix = '!' if flag == 'green' else '' + for x in channels.split(','): + if x[0]=='\\': + self.global_ignore.update( x.lstrip('\\').split('\\') ) # "\Defect" are modifiers not a selection requirement + else: + a += [ prefix+self.sort_uniq_ignores(x) ] + if len(a)>0: + self.selectionChannelNames += [ a ] + + + def addShowChannel(self, folder, channelname, tag): + self.channelNames += [channelname] + from CoolRunQuery.AtlRunQueryRun import Run + if not 'DQ' in Run.Fieldinfo: Run.Fieldinfo['DQ'] = {} + if not 'DefChannels' in Run.Fieldinfo['DQ']: Run.Fieldinfo['DQ']['DefChannels'] = [] + Run.Fieldinfo['DQ']['DefChannels'] += [channelname] + + + def __str__(self): + if self.applySelection: + return "SELOUT Checking the DQ defects %s" % ', '.join([', '.join(cn) for cn in self.selectionChannelNames]) + else: + return "Retrieving DQ defects %s" % ', '.join([', '.join(cn) for cn in self.selectionChannelNames]) + + def passes(self,values, key): + return True + + def rejectRun(self,run): + return super(DQDefectCondition,self).rejectRun(run) + + def mergeRanges(self,it): + """ two adjacent defect entries are merged into one""" + doMerge = False + if doMerge: + mergedByDefect = [] + x = it.next() + (openedat,openuntil) = (x.startlb,x.endlb) + for x in it: + if x.startlb == openuntil: + openuntil = x.endlb + else: + mergedByDefect += [(openedat, openuntil)] + openedat,openuntil = x.startlb,x.endlb + mergedByDefect += [(openedat, openuntil)] + return mergedByDefect + else: + return [(x.startlb,x.endlb) for x in it] + + + + def runAfterQuery(self,runlist): + """ + collects, sorts, and groups defects with LBs and comments + """ + + + from itertools import groupby + from operator import itemgetter, attrgetter + + for run in runlist: + + for k in self.ResultKey(): + # format of run.data[k] is + # [ <DataEntry with value = (defect_name, defect_comment or defect_composition)>, ...] + + #for x in run.data[k]: + # print " %r" % (x,) + if not k in run.data.keys(): + run.result[k] = {} + run.stats[k] = {} + + all_defects = sorted(list(set([x.value[0] for x in run.data[k]]))) + + grouped_defects = groupby(sorted(run.data[k],key=lambda x: x.value[0]),key = lambda x: x.value[0]) # first sort by defect name, then group by defect name + + desired_defects = [ (defect_name, self.mergeRanges(lblist)) for defect_name, lblist in grouped_defects ] + + run.stats[k] = { "defects": desired_defects, + "primaries": {} } + + primaries = {} # defect : list of primaries with lb-range + if run.runNr in self.primaries: + primgroups = groupby(sorted([p for p in self.primaries[run.runNr]],key=attrgetter('channel')),key=attrgetter('channel')) + primaries = dict([(p,list(l)) for p,l in primgroups]) + + for defect in all_defects: + if self.__db.defect_is_virtual(defect): + reps = [] + self.find_primaries(DD, self.__db.virtual_defect_logics[defect], primaries, defect, reps) + run.stats[k]["primaries"][defect] = reps + elif defect in primaries: + run.stats[k]["primaries"][defect] = [ DD("->%s" % def_obj.channel, def_obj.comment, def_obj.since.lumi, def_obj.until.lumi) for def_obj in primaries[defect] ] + + + #import pickle + #f = open("dev.pickle","w") + #pickle.dump(run.stats[k]["primaries"],f) + #f.close() + + #for x,l in run.stats[k]["primaries"].items(): + # print x + # for y in sorted(l): + # print " ",y + + + def find_primaries(self, DD, defect_logic, primaries, curpath, reps): + for pdef in defect_logic.primary_defects: + if not pdef in primaries: continue + pdefects = primaries[pdef] + for pdeflb in pdefects: + reps += [ DD("%s->%s" % (curpath, pdeflb.channel), pdeflb.comment, pdeflb.since.lumi, pdeflb.until.lumi) ] + for dep in defect_logic.dependencies: + self.find_primaries(DD, dep, primaries, curpath+"->"+dep.name, reps) + diff --git a/Database/CoolRunQuery/python/selector/AtlRunQuerySelectorEvents.py b/Database/CoolRunQuery/python/selector/AtlRunQuerySelectorEvents.py new file mode 100644 index 00000000000..0279adf7b25 --- /dev/null +++ b/Database/CoolRunQuery/python/selector/AtlRunQuerySelectorEvents.py @@ -0,0 +1,132 @@ +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + + +from time import time +import sys + +from CoolRunQuery.utils.AtlRunQueryIOV import IOVTime, IOVRange +from CoolRunQuery.utils.AtlRunQueryTimer import timer +from CoolRunQuery.utils.AtlRunQueryUtils import coolDbConn, runsOnServer, GetRanges, SmartRangeCalulator + +from .AtlRunQuerySelectorBase import Selector, Condition, RunLBBasedCondition, TimeBasedCondition, DataKey + +class EventSelector(RunLBBasedCondition): + def __init__(self, name, events=None): + self.events = events + super(EventSelector,self).__init__(name=name, + dbfolderkey='oracle::SFO-based', + channelKeys = [(0,'#Events','EFEvents')]) + if events: self.cutRange = GetRanges(events) + + + + def __str__(self): + if self.applySelection: return "SELOUT Checking if number of events matches %s" % self.events + else: return "Retrieving event numbers" + + def select(self, runlist): + + # some preparation: compile the show patterns + start = time() + + print self, + sys.stdout.flush() + newrunlist = [] + connection = coolDbConn.GetSFODBConnection() + cursor = connection.cursor() + cursor.arraysize = 1000 + + runnrlist = [r.runNr for r in runlist] + + from CoolRunQuery.AtlRunQuerySFO import GetSFO_NphysicseventsAll + with timer("GetSFO_NphysicseventsAll"): + events = GetSFO_NphysicseventsAll( cursor, runnrlist ) # { runnr: nPhysEvents } + + for run in runlist: + + iov = IOVRange(runStart=run.runNr, lbStart=1, runEnd=run.runNr+1, lbEnd=0) + + for k in self.ResultKey(): + if not run.runNr in events: + # the OVERLAP_EVENTS information is not yet available in the SFO (before Nov 15, 2009) + # use the inclusive number instead + run.addResult(k, "n.a.", iov, reject=False) + run.showDataIncomplete=True + continue + + nev = events[run.runNr] + + if self.ApplySelection(k) and not self.passes(nev,k): + run.addResult(k, self.prettyValue(nev,k), iov, reject=True) + continue + + run.addResult(k, self.prettyValue(nev,k), iov) + + rej = self.rejectRun(run) + + if not rej: + newrunlist += [run.runNr] + + runlist = [r for r in runlist if r.runNr in newrunlist] + + duration = time() - start + if self.applySelection: print " ==> %i runs found (%.2f sec)" % (len(runlist),duration) + else: print " ==> Done (%g sec)" % duration + return runlist + + def passes(self,values,key): + try: + val = int(values) + except ValueError: # if n.a. + self.selDataMissing = True + return True + for cr in self.cutRange: + if val>=cr[0] and val<=cr[1]: + return True + return False + + +class AllEventsSelector(RunLBBasedCondition): + def __init__(self, name, events=None): + self.events = events + self.showAllevents = False + super(AllEventsSelector,self).__init__(name=name, + dbfolderkey='COOLONL_TDAQ::/TDAQ/RunCtrl/EventCounters', + channelKeys = [(0,'#Events (incl. calib.)','EFEvents'), + (0,'#Events (streamed)','RecordedEvents'), + (0,'#L2A','L2Events')]) + if events: self.cutRange = GetRanges(events) + + def __str__(self): + if self.applySelection: return "SELOUT Checking if number of events matches %s" % self.events + else: return "Retrieving event numbers" + + def passes(self,values,key): + try: + val = int(values) + except ValueError: # if n.a. + self.selDataMissing = True + return True + for cr in self.cutRange: + if val>=cr[0] and val<=cr[1]: + return True + return False + +class L1EventsSelector(RunLBBasedCondition): + def __init__(self, name, events=None): + self.events = events + self.showL1events = False + super(L1EventsSelector,self).__init__(name=name, + dbfolderkey='COOLONL_TRIGGER::/TRIGGER/LVL1/CTPCORELBDATA', + channelKeys = [(0,'#L1A','GlobalL1AcceptCounter')]) + def setShowL1events(self): + self.showL1events = True + super(L1EventsSelector,self).setShowOutput() + + def __str__(self): + if self.applySelection: return "SELOUT Checking if number of L1A events matches %s" % self.events + else: return "Retrieving L1A numbers" + + def passes(self,values,key): + return True + diff --git a/Database/CoolRunQuery/python/selector/AtlRunQuerySelectorLhcOlc.py b/Database/CoolRunQuery/python/selector/AtlRunQuerySelectorLhcOlc.py new file mode 100644 index 00000000000..c1d6d93f2bc --- /dev/null +++ b/Database/CoolRunQuery/python/selector/AtlRunQuerySelectorLhcOlc.py @@ -0,0 +1,488 @@ +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + + +from CoolRunQuery.utils.AtlRunQueryIOV import IOVTime, IOVRange +from CoolRunQuery.utils.AtlRunQueryTimer import timer +from CoolRunQuery.utils.AtlRunQueryUtils import coolDbConn, runsOnServer, GetRanges, SmartRangeCalulator + +from .AtlRunQuerySelectorBase import Selector, Condition, RunLBBasedCondition, TimeBasedCondition, DataKey + +class LHCSelector(Selector): + def __init__(self, name, lhc=[], addArg=''): + super(LHCSelector,self).__init__(name) + self.condition = LHCCondition( 'lhc', lhc, addArg = addArg ) + self.showarg=addArg + if not lhc: + self.condition.applySelection = False + + def __str__(self): + return "\n%s" % self.condition + + def addShowSelector(self, addArg=''): + self.showarg=addArg + + def setShowOutput(self): + self.condition.setShowOutput(self.showarg) + + def select(self, runlist): + runlist = self.condition.select(runlist) + return runlist + +class LHCCondition(TimeBasedCondition): + def __init__(self, name, condition=[], channel=0, addArg=''): + ck = [(1,'lhc:fillnumber', 'FillNumber'), + (1,'lhc:stablebeams', 'StableBeams'), + (1,'lhc:beamenergy', 'BeamEnergyGeV')] + if addArg == 'all': ck += [(1,'lhc:beammode', 'BeamMode')] + elif addArg == 'min': ck = [(1,'lhc:stablebeams', 'StableBeams')] + super(LHCCondition,self).__init__(name=name, + dbfolderkey='COOLOFL_DCS::/LHC/DCS/FILLSTATE', + channelKeys = ck) + + # define arguments (after initialising base class!) + self.lhc = {} + for c in condition: + # format: 'fillnumber 891' or 'stablebeams 1', etc + lhcargs = c.split() + if len(lhcargs) == 2: + key = 'lhc:' + lhcargs[0].strip().lower() + # does it exist? + if not key in self.ResultKey(): + print 'ERROR: unknown LHC variable "%s"' % key + sys.exit(1) + + cond = lhcargs[1].strip() + if cond: + try: + isnum = float(cond[0]) + self.lhc[key] = GetRanges( cond ) + except ValueError: + self.lhc[key] = [[cond]] + pass + else: + print 'ERROR: unknown condition format for LHC: "%s" -> need two arguments separated by blank' % lhcargs + sys.exit(1) + + + + def setShowOutput(self, addArg ): + ck = ['lhc:fillnumber', 'lhc:stablebeams', 'lhc:beamenergy'] + if addArg == 'all': ck += ['lhc:beammode'] + elif addArg == 'min': ck = ['lhc:stablebeams'] + super(LHCCondition,self).setShowOutput() + + def __str__(self): + if self.applySelection: return "SELOUT Checking if LHC fill state information %s" % self.lhc + else: return "Retrieving LHC fill state information" + + def passes(self, value, k): + if not k in self.lhc: return True + if 'n.a.' in value: return True + try: + v = float(value) + # special treatment for E-beam = 7864 --> value for ASYNC + if v >= 7864: return False + + # iterate over conditions + for cr in self.lhc[k]: + if v >= cr[0] and v <= cr[1]: return True + except ValueError: + # value is a string + # note that it still can happen that the reference of the string is a number. For example, the + # beam energy can be returned as "ASYNC", when the two beams were in asynchronous mode. Need + # to cover such a case. + try: + cmin = float(self.lhc[k][0][0]) + return False # comparing number to string -> don't pass + except ValueError: + if value == self.lhc[k][0][0]: return True + pass + pass + return False + +class OLCFillParamsCondition(TimeBasedCondition): + def __init__(self, name, condition=[], channel=0): + super(OLCFillParamsCondition,self).__init__(name=name, + dbfolderkey='COOLONL_TDAQ::/TDAQ/OLC/LHC/FILLPARAMS', + channelKeys = [(0,'olc:beam1bunches', 'Beam1Bunches'), + (0,'olc:beam2bunches', 'Beam2Bunches'), + (0,'olc:collbunches', 'LuminousBunches'), + (0,'olc:bcidmask', 'BCIDmasks')]) + self.onl = {} + + def __str__(self): + if self.applySelection: return "SELOUT Checking if online lumi is %s" % self.olc + else: return "Retrieving online lumi information /TDAQ/OLC/LHC/FILLPARAMS" + + def passes(self,values,k): + if not k in self.onl: return True + if 'n.a.' in values: return True + if values == self.onl[k]: return True + return False + + def runAfterQuery(self,runlist): + for k in self.ResultKey(): + with timer("olc afterquery blocks prepare for: %s" % k): + for run in runlist: + run.stats[k] = {} + if not k in run.data: continue + blocks = [] + for entry in run.data[k]: + v = entry.value + if k!='olc:bcidmask': + v = int(v) + if len(blocks) > 0 and blocks[-1][0]==v and blocks[-1][2]==entry.startlb: + blocks[-1][2] = entry.endlb + else: + blocks += [ [v, entry.startlb, entry.endlb] ] + run.stats[k] = { "blocks" : blocks, "first" : run.data[k][0].value } + + + with timer("olc afterquery rest"): + from CoolRunQuery.utils.AtlRunQueryUtils import UnpackBCID + for run in runlist: + + if run.runNr < 151260: continue # OLC information was not in COOL before this run + + # these contain the number of bunches for each IOV [(nb,lbstart,lbend)(...)...] + xb1 = run.stats['olc:beam1bunches']['blocks'] + xb2 = run.stats['olc:beam2bunches']['blocks'] + xbc = run.stats['olc:collbunches']['blocks'] + + # loop over BCID mask + bcidmask = run.stats['olc:bcidmask']['blocks'] + for i in xrange(len(bcidmask)): + (v,lbstart,lbend) = bcidmask[i] + + # number of bunches + for nb1, b, e in xb1: + if lbstart>=b and lbstart<e: break + for nb2, b, e in xb2: + if lbstart>=b and lbstart<e: break + for nbc, b, e in xbc: + if lbstart>=b and lbstart<e: break + + # unpack BCID mask + if len(v) == 2 * (nb1 + nb2 + nbc): + beam1, beam2, beam12 = UnpackBCID( v, nb1, nb2, nbc ) + else: + print "WARNING, bcidMask inconsistent",nb1, nb2, nbc,"should add up to half of",len(v) + beam1, beam2, beam12 = ([],[],[]) + + # refill run stats + bcidmask[i] = ((nb1, nb2, nbc), (beam1, beam2, beam12), lbstart, lbend) + +class OLCLBDataCondition(TimeBasedCondition): + def __init__(self, name, condition=[]): + super(OLCLBDataCondition,self).__init__(name=name, + dbfolderkey='COOLONL_TDAQ::/TDAQ/OLC/LHC/LBDATA', + channelKeys = [(1,'olc:beam1intensity', 'Beam1Intensity'), + (1,'olc:beam2intensity', 'Beam2Intensity')]) + #(1,'olc:beam1intensitybct', 'Beam1IntensityAll'), + #(1,'olc:beam2intensitybct', 'Beam2IntensityAll')]) + #(0,'olc:invlivetime', 'Beam1InvLifetime')]) + #(0,'olc:invlivetime', 'Beam2InvLifetime')]) + self.onl = {} + + def __str__(self): + if self.applySelection: return "SELOUT Checking if online lumi is %s" % self.olc + else: return "Retrieving online lumi information /TDAQ/OLC/LHC/LBDATA" + + def passes(self,values,k): + if not k in self.onl: return True + if 'n.a.' in values: return True + if values == self.onl[k]: return True + return False + + def runAfterQuery(self,runlist): + for k in self.ResultKey(): + for run in runlist: + blocks = [] + first = 0 + if len(run.data[k])>0: + for entry in run.data[k]: + v = entry.value + if len(blocks) > 0 and blocks[-1][0]==v and blocks[-1][2]==entry.startlb: + blocks[-1][2] = entry.endlb + else: + blocks += [ [v, entry.startlb, entry.endlb] ] + first = run.data[k][0].value + run.stats[k] = { "blocks" : blocks, "first" : first } + +# moved from LUMINOSITY timestamp-based to LB-based folder +#... class OLCLumiCondition(TimeBasedCondition): +class OLCLumiCondition(RunLBBasedCondition): + def __init__(self, name, condition=[], channel=0): + + self.condition = condition + name, sep, channel = name.partition(' ') + try: + channel = int(channel) + except ValueError: + channel = 0 + pass + + # the key + # interpret condition: reference luminosity: ub-1, default: ub-1 + # format: 1ub, 100nb, 1mb+, 1pb-, etc + units = [ 'mb', 'ub', 'nb', 'pb', 'fb', 'ab', 'b' ] + factoub = [ 1e-3, 1, 1e3, 1e6, 1e9, 1e12, 1e-6 ] + self.complumi = -1 + self.compsign = +1 + f = 1 + if self.condition: + c = self.condition[0].strip() + for iu,u in enumerate(units): + if u in c: + f = factoub[iu] + c = c.replace(u,'') + break + + try: + if '+' in c: + c = c.replace('+','').strip() + self.complumi = float(c) + elif '-' in c: + self.compsign = -1 + c = c.replace('-','').strip() + self.complumi = float(c) + except ValueError: + print "ERROR: in 'olc' condition: %s" % self.condition + sys.exit(1) + self.complumi *= f # in ub-1 + + super(OLCLumiCondition,self).__init__(name=name, + dbfolderkey='COOLONL_TRIGGER::/TRIGGER/LUMI/LBLESTONL', + channelKeys = [(channel,'olc:lumi:%i' % (channel), 'LBAvInstLumi')]) + # ----------------------------------------------------------------------------------------------------------- + # old, timestamp based folder + # super(OLCLumiCondition,self).__init__(name=name, + # dbfolderkey='COOLONL_TDAQ::/TDAQ/OLC/LUMINOSITY', + # channelKeys = [(channel,'olc:lumi:%i' % channel, 'LBAvInstLumPhys')]) + # ----------------------------------------------------------------------------------------------------------- + self.olc = {} + + def __str__(self): + if self.applySelection: return "SELOUT Checking if online lumi is %s" % self.condition + else: return "Retrieving online lumi information /TRIGGER/LUMI/LBLESTONL" + + def passes(self,values,k): + return True + if not k in self.olc: return True + if 'n.a.' in values: return True + + if values == self.olc[k]: return True + return False + + def runAfterQuery(self, runlist): + + # correct "old" runs for uncalibrated luminosity + k = self.ResultKey()[0] # lumi + kst = 'lhc:stablebeams' + for ir,run in enumerate(runlist): + for entry in run.data[k]: + if entry.startlb == 0: continue + if entry.value != 'n.a.': + if run.runNr <= 158632: entry.value *= 1.13 + + if not self.condition: return + + # compute integrated luminosity + # given is the delivered luminosity in units of 10^30 cm-2 = 1 ub-1 + # insert events per LB + vetolist = [] + for ir,run in enumerate(runlist): + yvecInt = [] # olc per lb in 10^27 cm-2s-1 + xvecStb = [] + for entry in run.data[k]: + assert entry.startlb != 0, 'entry should not start at LB=0' + val = 0 + if entry.value != 'n.a.': + val = max(0,entry.value) * 1.e3 # unit of val was: 10^30 cm-2 s-1, transform to: 10^27 cm-2s-1 + lbs = range(entry.startlb,entry.endlb) + yvecInt += len(lbs)*[val] + + # check LBs with stable beam + print run.data[kst].atLB(entry.startlb) + stable_beam = run.data[kst].atLB(entry.startlb)[0].value + if 'true' in stable_beam.lower(): + xvecStb += lbs + + # correct for time span + intlumi = 0 + for idx,(lbtime,lbendtime) in enumerate(run.lbtimes): + lb=idx+1 + if lb in xvecStb: + # unit of yvec was: 10^27 cm-2 s-1 -> 10^30 cm-2 = ub-1 + yvecInt[idx] *= (float(lbendtime)-float(lbtime))/1.E9/1000. + intlumi += yvecInt[idx] + + if not (intlumi > self.compsign*self.complumi or -1*intlumi > self.compsign*self.complumi): + vetolist.append(ir) + + # remove vetoed runs + ic = 0 + vetolist.sort() + for ir in vetolist: + del runlist[ir-ic] + ic += 1 + + def prettyValue(self, value, key): + if value=='n.a.': return value + return float(value) # unit is ub-1 s-1 + +class OLCLumiSelector(Selector): + def __init__(self, name, olclumi=[]): + super(OLCLumiSelector,self).__init__(name) + self.condition = OLCLumiCondition( 'olclumi', olclumi ) + if not olclumi: + self.condition.applySelection = False + + def setShowOutput(self): + self.condition.setShowOutput() + + def runAfterQuery(self, runlist): + self.condition.runAfterQuery( runlist ) + + def select(self, runlist): + runlist = self.condition.select(runlist) + return runlist + +class LuminositySelector(RunLBBasedCondition): + def __init__(self, name, lumi=None): + + name, tag = (name.split(None,1) + [""])[:2] + + channel, condtag = self.__interpretTag(tag) + + self._dbfolderkey='COOLOFL_TRIGGER::/TRIGGER/OFLLUMI/LBLESTOFL' + if condtag: self._dbfolderkey += "#" + condtag + + self._channelKeys = [(channel,'ofllumi:%i:%s' % (channel,condtag), 'LBAvInstLumi')] + + + super(LuminositySelector,self).__init__(name=name, + dbfolderkey = self._dbfolderkey, + channelKeys = self._channelKeys) + + if lumi: + self.cutRange = GetRanges(lumi) + self.applySelection = True + + def addShowTag(self, tag): + channel, condtag = self.__interpretTag(tag) + self._dbfolderkey='COOLOFL_TRIGGER::/TRIGGER/OFLLUMI/LBLESTOFL' + if condtag: self._dbfolderkey += "#" + condtag + self._channelKeys = [(channel,'ofllumi:%i:%s' % (channel,condtag), 'LBAvInstLumi')] + + def initialize(self): + print "Setting channelkeys",self._channelKeys + self.setSchemaFolderTag(self._dbfolderkey) + self.setChannelKeys(self._channelKeys) + + def __interpretTag(self, tag): + # default settings + channel = 0 # 'ATLAS_PREFERRED' algorithm +# condtag = "OflLumi-8TeV-002" # uses vdM scan calibration + condtag = "OflLumi-UPD2-004" # temporary + l = tag.split() + if len(l) == 1: + # format: "luminosity <channel_number>" OR "luminosity <COOL tag>" + try: channel = int(l[0]) + except ValueError: condtag = l[0] + elif len(l) == 2: + # format: "luminosity <channel_number> <COOL tag>" + try: channel = int(l[0]) + except ValueError: channel = 0 + condtag = l[1] + return channel, condtag + + + + + def __str__(self): + if self.applySelection: return "SELOUT Checking if number of events matches %r" % self.cutRange + else: return "Retrieving lumi numbers" + + def passes(self,values,key): + try: + val = int(values) + except ValueError: # if n.a. + self.selDataMissing = True + return True + for cr in self.cutRange: + if val>=cr[0] and val<=cr[1]: + return True + return False + +class BeamspotSelector(RunLBBasedCondition): + def __init__(self, name, bs=None, args=""): + self.bs = bs + args = args.split() + + # defaults + folder = 'COOLOFL_INDET::/Indet/Beampos' + # self.condtag = 'IndetBeampos-ES1-UPD2' + self.condtag = 'IndetBeampos-ES1-UPD2-02' # new default tag (operational since rel 17, run 188902, 7 Sep) + self.isOffline = True + + if args: # more information: online and/or COOL tag + # check if online + if args[0].lower().startswith('onl'): + # online format: "online MyTag" or "online" + self.isOffline = False + folder = 'COOLONL_INDET::/Indet/Onl/Beampos' + self.condtag = 'IndetBeamposOnl-HLT-UPD1-001-00' # default tag + if len(args)>1: + if args[1]=='live': + self.condtag = 'IndetBeamposOnl-LiveMon-001-00' + else: + self.condtag = args[1] + else: + # assume second entry is COOL tag + self.condtag = args[0] + + super(BeamspotSelector,self).__init__(name = name, + dbfolderkey = folder, + channelKeys = [ + (0,'bs:Status','status'), + (0,'bs:Pos-X', ('posX','posXErr')), + (0,'bs:Pos-Y', ('posY','posYErr')), + (0,'bs:Pos-Z', ('posZ','posZErr')), + (0,'bs:Sig-X', ('sigmaX','sigmaXErr')), + (0,'bs:Sig-Y', ('sigmaY','sigmaYErr')), + (0,'bs:Sig-Z', ('sigmaZ','sigmaZErr')), + (0,'bs:Sig-XY',('sigmaXY','sigmaXYErr')), + (0,'bs:Tilt-X',('tiltX','tiltXErr')), + (0,'bs:Tilt-Y',('tiltY','tiltYErr')) + ]) + + if bs: self.cutRange = GetRanges(bs) + + def __str__(self): + if self.applySelection: return "SELOUT Checking if %s beamspot information matches %s" % ("offline" if self.isOffline else "online", self.bs) + else: return "Retrieving %s beamspot information" % ("offline" if self.isOffline else "online",) + + def prettyValue(self, value, key): + if type(value)==tuple: + return tuple(map(float,value)) + return float(value) + + + def passes(self,values,key): + try: + val = int(values) + except ValueError: # if n.a. + self.selDataMissing = True + return True + for cr in self.cutRange: + if val>=cr[0] and val<=cr[1]: + return True + return False + + def runAfterQuery(self,runlist): + whatitis = 'offline' if self.isOffline else 'online' + from CoolRunQuery.AtlRunQueryRun import Run + Run.BeamspotSource = '%s, COOL tag: %s' % (whatitis, self.condtag) + for run in runlist: + run.stats['Beamspot'] = whatitis diff --git a/Database/CoolRunQuery/python/selector/AtlRunQuerySelectorMisc.py b/Database/CoolRunQuery/python/selector/AtlRunQuerySelectorMisc.py new file mode 100644 index 00000000000..481077cd95e --- /dev/null +++ b/Database/CoolRunQuery/python/selector/AtlRunQuerySelectorMisc.py @@ -0,0 +1,335 @@ +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + + +import re,sys + +from CoolRunQuery.AtlRunQueryRun import Run +from CoolRunQuery.utils.AtlRunQueryIOV import IOVTime, IOVRange +from CoolRunQuery.utils.AtlRunQueryTimer import timer +from CoolRunQuery.utils.AtlRunQueryUtils import coolDbConn, runsOnServer, GetRanges, SmartRangeCalulator + +from .AtlRunQuerySelectorBase import Selector, Condition, RunLBBasedCondition, TimeBasedCondition, DataKey + + +class FilenameSelector(RunLBBasedCondition): + def __init__(self, name, projecttag=None): + self.filenametag = projecttag + self.fntpatterns = None + if projecttag: + self.fntpatterns = [re.compile(pt.strip().replace('*','.*').replace('?','.'),re.I) for pt in projecttag.split(',')] + super(FilenameSelector,self).__init__(name=name, + dbfolderkey='COOLONL_TDAQ::/TDAQ/RunCtrl/SOR_Params', + channelKeys = [(0, 'Project tag','FilenameTag')]) + def __str__(self): + if self.applySelection: return 'SELOUT Checking if the filename tag matches "%s"' % self.filenametag + else: return "Retrieving filenametag" + def passes(self,value,key): + for p in self.fntpatterns: + if p.match(value): return True + return False + +class PartitionSelector(RunLBBasedCondition): + def __init__(self, name, partition=None): + self.partition = partition + super(PartitionSelector,self).__init__(name=name, + dbfolderkey='COOLONL_TDAQ::/TDAQ/RunCtrl/EventCounters', + channelKeys = [(0,'Partition','PartitionName')]) + + def __str__(self): + if self.applySelection: return 'SELOUT Checking if partition name matches "%s"' % self.partition + else: return "Retrieving partition name" + + def passes(self,value,key): + if value=='n.a.': + self.selDataMissing = True + return True + return value==self.partition + +class ReadyForPhysicsSelector(RunLBBasedCondition): + def __init__(self, name, readyforphysics=None): + self.readyforphysics = readyforphysics + super(ReadyForPhysicsSelector,self).__init__(name=name, + dbfolderkey='COOLONL_TDAQ::/TDAQ/RunCtrl/DataTakingMode', + channelKeys = [(0,'Ready for physics','ReadyForPhysics')]) + + def __str__(self): + if self.applySelection: return 'SELOUT Checking if ReadyForPhysics flag matches "%s"' % self.readyforphysics + else: return "Retrieving ReadyForPhysics flag" + + def passes(self,value,key): + if value=='n.a.': + self.selDataMissing = True + return True + return value==self.readyforphysics + + +class DurationSelector(Selector): + def __init__(self,name,duration): + self.geReq = (duration[-1]!='-') + self.duration = int(duration.rstrip('+-')) + super(DurationSelector,self).__init__(name) + def __str__(self): + return "SELOUT Checking if duration of run is %s than %i seconds" % ("more" if self.geReq else "less",self.duration) + def select(self, runlist): + print self, + sys.stdout.flush() + if self.geReq: + rmlist = [r for r in runlist if (r.eor-r.sor)/1E9<self.duration] + else: + rmlist = [r for r in runlist if (r.eor-r.sor)/1E9>self.duration] + for r in rmlist: runlist.remove(r) + if self.applySelection: print " ==> %i runs selected" % len(runlist) + return runlist + + +class DetectorSelector(RunLBBasedCondition): + def __init__(self, name, dmin=None, dmout=None): + self.bm = self.bp = self.bmany = 0 + if not dmin: dmin = [] + if not dmout: dmout = [] + for m in dmin+dmout: + if 'A' in m: continue + self.bm |= int(m) + for m in dmin: + if 'A' in m: continue + self.bp |= int(m) + for m in dmin: + if not 'A' in m: continue + self.bmany |= int(m.rstrip('A')) + + super(DetectorSelector,self).__init__(name=name, + dbfolderkey='COOLONL_TDAQ::/TDAQ/RunCtrl/SOR_Params', + channelKeys = [(0, 'Detector systems', 'DetectorMask')]) + + self.data_keys[0]._type = DataKey.DETECTOR + + + + def __str__(self): + if self.applySelection: + if self.bm!=0 and self.bmany!=0: + return "SELOUT Checking if [detector mask & %i] matches %i and [detector mask & %i] is greater 0" % (self.bm,self.bp,self.bmany) + elif self.bm==0: + return "SELOUT Checking if [detector mask & %i] is greater 0" % self.bmany + else: + return "SELOUT Checking if [detector mask & %i] matches %i" % (self.bm,self.bp) + else: return "Retrieving detector mask" + + def passes(self,values,key): + try: + val = int(values) + except ValueError: + self.selDataMissing = True + return True + retval = True + if self.bm!=0: retval &= ((val & self.bm) == self.bp) + if self.bmany!=0: retval &= ((val & self.bmany) != 0) + return retval + + def prettyValue(self, value, key): + return value + + + + +class BFieldCondition(TimeBasedCondition): + def __init__(self, name, condition=None, channel=0, resDictKey=''): + super(BFieldCondition,self).__init__(name, + dbfolderkey='COOLOFL_DCS::/EXT/DCS/MAGNETS/SENSORDATA', + channelKeys = [(channel,resDictKey,'value')]) + if condition: + self.cutRange = GetRanges(condition) + def __str__(self): + if self.applySelection: + if self.cutRange[0][1] > 1000000: txt = '[%.0f,+inf]' % (self.cutRange[0][0]) + else: txt = '[%.0f,%0.f]' % (self.cutRange[0][0],self.cutRange[0][1]) + return "SELOUT Checking if the %s is within %s" % (', '.join(self.ResultKey()), txt) + else: return "Retrieving magnet currents %s" % self.ResultKey() + + def passes(self,values,key): + try: + val = abs(float(values)) + except ValueError: + self.selDataMissing = True + return True + for cr in self.cutRange: + if val>=cr[0] and val<=cr[1]: return True + return False + + def prettyValue(self, value, key): + if value=='n.a.': return value + return round(float(value),2) + + +class BFieldSelector(Selector): + def __init__(self, name, bf=None): + super(BFieldSelector,self).__init__(name) + self.conditions = [] + if bf: + if 'solenoidon' in bf or 'solenoidoff' in bf: + tmp = [] + if 'solenoidon' in bf: tmp += ['7700+'] + elif 'solenoidoff' in bf: tmp += ['10-'] + self.conditions += [ BFieldCondition('csolcur', ','.join(tmp), channel = 1, resDictKey = 'SolCurrent') ] + if 'toroidon' in bf or 'toroidoff' in bf: + tmp = [] + if 'toroidon' in bf: tmp += ['20000+'] + elif 'toroidoff' in bf: tmp += ['10-'] + self.conditions += [ BFieldCondition('ctorcur', ','.join(tmp), channel = 3, resDictKey = 'TorCurrent') ] + else: + self.conditions += [ BFieldCondition('csolcur', channel = 1, resDictKey = 'SolCurrent') ] + self.conditions += [ BFieldCondition('ctorcur', channel = 3, resDictKey = 'TorCurrent') ] + for sel in self.conditions: + sel.setShowOutput() + sel.applySelection = False + + def __str__(self): + s = "" + for sel in self.conditions: + s += "\n%s" % sel + return s + + def setShowOutput(self): + for cond in self.conditions: + cond.setShowOutput() + + def addShowSelector(self): + sol = tor = None + for s in self.conditions: + if s.name == 'csolcur': sol = s + if s.name == 'ctorcur': tor = s + if not sol: + sol = BFieldCondition('csolcur', channel = 1, resDictKey = 'SolCurrent') + sol.applySelection = False + self.conditions += [ sol ] + if not tor: + tor = BFieldCondition('ctorcur', channel = 3, resDictKey = 'TorCurrent') + tor.applySelection = False + self.conditions += [ tor ] + sol.setShowOutput() + tor.setShowOutput() + + def select(self, runlist): + for sel in self.conditions: + runlist = sel.select(runlist) + return runlist + + +class BPMSelector(Selector): + def __init__(self, name, events=None): + self.name = name + self.selpattern = [] + super(BPMSelector,self).__init__(name) + + def __str__(self): return "Retrieving Beam Position Monitor values from PVSS archive" + def passes(self,values,key): return True + def setShowOutput(self): pass + def select(self, runlist): return runlist + + def runAfterQuery(self,runlist): + # do show selection here + from CoolRunQuery.AtlRunQueryPVSS import GetPVSS_BPMs + pvssdb = coolDbConn.GetPVSSDBConnection() + cursor = pvssdb.cursor() + cursor.arraysize = 1000 + + pvssretdico = {} + for r in runlist: + t = gmtime(r.sor/1.E9) + sor = "%02i-%02i-%4i %02i:%02i:%02i" % (t[2], t[1], t[0], t[3], t[4], t[5]) + t = gmtime(r.eor/1.E9) + eor = "%02i-%02i-%4i %02i:%02i:%02i" % (t[2], t[1], t[0], t[3], t[4], t[5]) + + # retrieve values + res = GetPVSS_BPMs( cursor, sor, eor ) + r.addResult('BPM', res ) + + Run.AddToShowOrder(DataKey('BPM')) + + + +class DatasetsSelector(Selector): + def __init__(self, name, events=None): + self.name = name + self.selpattern = [] + self.showCAFLinks = False + super(DatasetsSelector,self).__init__(name) + + def __str__(self): return "Retrieving datasets from Tier-0 DB" + def passes(self,values,key): return True + def setShowOutput(self): pass + def select(self, runlist): return runlist + def addShowDatasetPattern(self, pattern = ''): + # format: '*NTUP*,*dESD*,... [caf]' + if pattern.lower() == 'caf': + self.showCAFLinks = True + elif pattern: + # first check if 'caf' + sp = pattern.split() + if len(sp)>1: + if 'caf' == sp[0].lower(): + self.showCAFLinks = True + pattern = sp[1] + elif 'caf' == sp[1].lower(): + self.showCAFLinks = True + pattern = sp[0] + # sanity check + if len(sp) != 2 or not self.showCAFLinks: + print 'ERROR: wrong format in "show dataset". Usage: "show dataset [pattern] [caf]"' + sys.exit(1) + + self.selpattern = pattern.split(',') + + def runAfterQuery(self,runlist): + # do show selection here + from CoolRunQuery.AtlRunQueryTier0 import GetTier0_allDatasets + runnrlist = [r.runNr for r in runlist] + tier0connection = coolDbConn.GetTier0DBConnection() + tier0retdico = GetTier0_allDatasets( tier0connection.cursor(), runnrlist, self.selpattern ) + + for run in runlist: # go through old runlist and see + if tier0retdico.has_key( run.runNr ): + run.addResult('Datasets', tier0retdico[run.runNr]) + else: + run.addResult('Datasets', {}) + + Run.AddToShowOrder(DataKey('Datasets')) + Run.showCAFLinks = self.showCAFLinks + + + +class LArcondSelector(RunLBBasedCondition): + def __init__(self, name, larcond=[]): + super(LArcondSelector,self).__init__(name=name, + dbfolderkey='COOLONL_LAR::/LAR/Configuration/RunLog', + channelKeys = [(0,'lar:runtype', 'runType'), + (0,'lar:nsamples', 'nbOfSamples'), + (0,'lar:format', 'format')]) + # define arguments (after initialising bas class!) + self.larcond = {} + if larcond: + for c in larcond: + # format: 'nsamples 7' or 'runtype 1', etc + larcondargs = c.split() + if len(larcondargs) == 2: + key = 'lar:' + larcondargs[0].strip().lower() + # does it exist? + if not key in self.ResultKey(): + print 'ERROR: unknown larcond variable "%s"' % key + sys.exit(1) + self.larcond[key] = larcondargs[1].strip() + else: + print 'ERROR: unknown condition format for larcond: "%s" -> need two arguments separated by blank' % larcondargs + sys.exit(1) + + + def __str__(self): + if self.applySelection: return "SELOUT Checking if LAr condition matches %s" % self.larcond + else: return "Retrieving LAr run conditions" + + def passes(self,values,key): + if self.larcond.has_key(key.lower().strip()): + if values.strip() == self.larcond[key.lower().strip()]: return True + else: return False + return True + diff --git a/Database/CoolRunQuery/python/selector/AtlRunQuerySelectorRuntime.py b/Database/CoolRunQuery/python/selector/AtlRunQuerySelectorRuntime.py new file mode 100644 index 00000000000..86e273b52bd --- /dev/null +++ b/Database/CoolRunQuery/python/selector/AtlRunQuerySelectorRuntime.py @@ -0,0 +1,133 @@ +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + + +from time import time +import sys + +from PyCool import cool + +from CoolRunQuery.utils.AtlRunQueryIOV import IOVTime, IOVRange +from CoolRunQuery.utils.AtlRunQueryTimer import timer +from CoolRunQuery.utils.AtlRunQueryUtils import coolDbConn, runsOnServer, GetRanges, SmartRangeCalulator + +from .AtlRunQuerySelectorBase import Selector, Condition, RunLBBasedCondition, TimeBasedCondition, DataKey + +from CoolRunQuery.AtlRunQueryRun import Run + +class RunTimeSelector(Selector): + def __init__(self, name, runlist): + super(RunTimeSelector,self).__init__(name) + if not runlist: runlist = ['-'] + runlist = ','.join(runlist).split(',') + runlist = [rr for rr in runlist if not rr.startswith('last')] + self.runranges = GetRanges(','.join(runlist)) + + def __str__(self): + rr = [] + for r in self.runranges: + if r[0]==r[1]: rr += [ str(r[0]) ] + elif r[0]==r[1]-1: rr += [ '%i, %i' % tuple(r) ] + else: rr += [ '%i-%i' % tuple(r) ] + return "SELOUT Checking for runs in %s" % ', '.join(rr) + + + def select(self): + runlist = [] + + start = time() + folder = coolDbConn.GetDBConn(schema="COOLONL_TRIGGER",db=self.db).getFolder('/TRIGGER/LUMI/LBLB') + print self, + sys.stdout.flush() + currentRun = None + for rr in self.runranges: + objs = folder.browseObjects( rr[0] << 32, ((rr[1]+1) << 32)-1, cool.ChannelSelection(0)) + while objs.goToNext(): + obj=objs.currentRef() + payload=obj.payload() + runNr,lbNr = RunTimeSelector.runlb(obj.since()) + if lbNr==0: lbNr=1 # this is an aweful hack to make MC work (there only one LB exists in LBLB and it is nr 0) need to rethink this + if not currentRun or runNr > currentRun.runNr: + if currentRun: + currentRun.eor = currentEOR + runlist.append(currentRun) + currentRun = Run(runNr) + currentRun.sor = payload['StartTime'] + currentRun.lbtimes.extend([(0,0)]*(lbNr-len(currentRun.lbtimes))) + currentRun.lbtimes[lbNr-1] = ( payload['StartTime'], payload['EndTime'] ) + currentRun.lastlb = lbNr + currentEOR = payload['EndTime'] + if currentRun: + currentRun.eor = currentEOR + runlist.append(currentRun) + runlist.sort() + + duration = time() - start + print " ==> %i runs found (%.2f sec)" % (len(runlist),duration) + return runlist + + @staticmethod + def runlb(time): + run = time>>32 + lb = time&0xFFFFFFFF + return (run,lb) + + def runNrFromTime(self,timeiov): + listOfCoveredRuns = [] + runlist = self.runTimes.keys() + runlist.sort() + lastEOR = 0 + for rt in runlist: + x = self.runTimes[rt] + if timeiov[0]>=x[0] and timeiov[1]<x[1] or timeiov[0]<x[0] and timeiov[1]>x[0]: + listOfCoveredRuns += [rt] + lastEOR = x[1] + return (listOfCoveredRuns,lastEOR) + + +class TimeRunSelector(Selector): + def __init__(self, name, timelist): + super(TimeRunSelector,self).__init__(name) + self.timelist = ','.join(timelist) + + def select(self): + start = time() + runlist = [] + folder = coolDbConn.GetDBConn(schema="COOLONL_TRIGGER", db=self.db).getFolder('/TRIGGER/LUMI/LBTIME') + print 'SELOUT Checking for runs in time range "%s"' % self.timelist, + sys.stdout.flush() + ranges = GetRanges(self.timelist, maxval=long(time()*1E09)) + currentRun = None + for rr in ranges: + objs = folder.browseObjects( rr[0], rr[1]+86400000000000L, cool.ChannelSelection(0)) + while objs.goToNext(): + obj=objs.currentRef() + payload=obj.payload() + runNr = int(payload['Run']) + if runNr==0: continue # mistakenly runnr=0 was stored + + if runNr>1<<30: + # there is a problem with runs between + # usetimes 2009-04-14:00:00:00 2009-04-16:13:00:00 + # there the runnumbers are off the chart (> 1<<30) + continue + + if not currentRun or runNr != currentRun.runNr: + if currentRun: + currentRun.eor = currentEOR + runlist.append(currentRun) + currentRun = Run(runNr) + currentRun.sor = obj.since() + lbNr = int(payload['LumiBlock']) + currentRun.lbtimes.extend([(0,0)]*(lbNr-len(currentRun.lbtimes))) + currentRun.lbtimes[lbNr-1] = ( obj.since(), obj.until() ) + currentRun.lastlb = lbNr + currentEOR = obj.until() + if currentRun: + currentRun.eor = currentEOR + runlist.append(currentRun) + + runlist.sort() + duration = time() - start + print " ==> %i runs selected (%g sec)" % (len(runlist), duration) + return runlist + diff --git a/Database/CoolRunQuery/python/selector/AtlRunQuerySelectorStreams.py b/Database/CoolRunQuery/python/selector/AtlRunQuerySelectorStreams.py new file mode 100644 index 00000000000..abeb32bc51d --- /dev/null +++ b/Database/CoolRunQuery/python/selector/AtlRunQuerySelectorStreams.py @@ -0,0 +1,303 @@ +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + + +import re, sys +try: + Set = set +except NameError: + from sets import Set +from time import time + +from CoolRunQuery.utils.AtlRunQueryIOV import IOVTime, IOVRange +from CoolRunQuery.utils.AtlRunQueryTimer import timer +from CoolRunQuery.utils.AtlRunQueryUtils import coolDbConn, runsOnServer, GetRanges, SmartRangeCalulator + +from .AtlRunQuerySelectorBase import Selector, Condition, RunLBBasedCondition, TimeBasedCondition, DataKey + +from CoolRunQuery.AtlRunQueryRun import Run + + +class StreamSelector(Selector): + def __init__(self, name, streams=[]): + # streams can be ["*RPC*", "phy* 10k", "deb*,cal* 100k"] + # will select runs that satiesfy an 'and' of all patterns in the list + # a pattern without number are checked for existence + # a pattern with number will be checked against the sum of the events in all matching streams + + self.streams = ' and '.join(streams) + self.selstreamspatterns = [] + self.showstreampatterns = [] + + # selstreampatterns is a list of (pattern,nevents) tuples + for s in streams: + pattern = s.split()[0].replace(',','|').replace('*','.*').replace('%','.*').replace('?','.') + negate = pattern[0]=='!' + pattern = pattern.lstrip('!') + p = re.compile(pattern) + nevt = (s.split()+[None])[1] + self.selstreamspatterns += [(p,nevt,negate,pattern)] + + super(StreamSelector,self).__init__(name) + + def setShowOutput(self): + pass + + def addShowStreamPattern(self, streampattern="*"): + self.showstreampatterns += streampattern.replace('*','.*').replace('%','.*').replace('?','.').split(',') + + def __str__(self): + if len(self.streams)!=0: return 'SELOUT Checking if the stream name matches "%s"' % self.streams + else: return "Retrieving stream names that match '%s'" % ','.join(self.showstreampatterns) + + def select(self, runlist): + + # some preparation: compile the show patterns + start = time() + + if '.*' in self.showstreampatterns: + compiledShowPatterns = [re.compile('.*')] + else: + compiledShowPatterns = [re.compile(p) for p in self.showstreampatterns] + + # we disable the access to everything that is not needed if we only select + ShowStreams = False + useTier0 = False + if len(compiledShowPatterns)>0: + ShowStreams = True + useTier0 = True + + + print self, + sys.stdout.flush() + newrunlist = [] + allStreams = Set() # list of all the streams that are in the selected runs + connection = coolDbConn.GetSFODBConnection() + cursor = connection.cursor() + cursor.arraysize = 1000 + + runnrlist = [r.runNr for r in runlist] + + from CoolRunQuery.AtlRunQuerySFO import GetSFO_streamsAll,GetSFO_filesAll + with timer('GetSFO_streamsAll'): + streamsall = GetSFO_streamsAll( cursor, runnrlist ) # { runnr: [streams] } + with timer('GetSFO_filesAll'): + filesall = GetSFO_filesAll( cursor, runnrlist ) # [(COUNT(FILESIZE), SUM(FILESIZE), SUM(NREVENTS))] + + if ShowStreams: + from CoolRunQuery.AtlRunQuerySFO import GetSFO_LBsAll,GetSFO_NeventsAll,GetSFO_overlapAll + with timer('GetSFO_LBsAll'): + lbinfoall = GetSFO_LBsAll( cursor, runnrlist ) # [(MIN(LUMIBLOCKNR), MAX(LUMIBLOCKNR), #LUMIBLOCKS)] + with timer('GetSFO_overlapAll'): + overlapall = GetSFO_overlapAll( cursor, runnrlist ) # [(SUM(OVERLAP_EVENTS))] + smallrunnrlist=[] + for r in runnrlist: # go through old runlist and see + if not r in streamsall: continue + for s in streamsall[r]: + if r in lbinfoall and s in lbinfoall[r] and lbinfoall[r][s][1]>0: + smallrunnrlist += [r] + break + with timer('GetSFO_NeventsAll'): + neventsall = GetSFO_NeventsAll( cursor, smallrunnrlist ) # [(LUMIBLOCKNR, NREVENTS)] + + + + for run in runlist: # go through old runlist and see + + streams = [] # names of all streams in this run + strsize = [] # size of streams in this run + strevents = [] # nr of events in this run + if run.runNr in streamsall: + streams = streamsall[run.runNr] + for s in streams: + try: nfiles, size, events = filesall[run.runNr][s] + except: nfiles, size, events = (0,0,0) + strsize += [size] + strevents += [events] + + + + if ShowStreams: + from CoolRunQuery.utils.AtlRunQueryUtils import Matrix + + nst = len(streams) + stovmat = Matrix(nst,nst) # overlap matrix + for i,s in enumerate(streams): + run.stats['STR:'+s] = {} + + # fill overlaps into matrix + for j,s2 in enumerate(streams): + try: eventsij = overlapall[run.runNr][s][s2] + except KeyError: + eventsij = 0 + if i==j and events: + eventsij = events + stovmat.setitem(i,j,float(eventsij)) + stovmat.setitem(j,i,float(eventsij)) # symmetrise matrix + + + # read number of events per LB + minlb, maxlb, lbs = (0,0,1) + try: minlb, maxlb, lbs = lbinfoall[run.runNr][s] + except KeyError: pass + + # if minlb==maxlb==0 -> no file closure at LB boundary + if minlb == 0 and maxlb == 0: + run.stats['STR:'+s]['LBRecInfo'] = None; + continue + else: + lbevcount = '<tr>' + result = neventsall[run.runNr][s] #[ (lb,nev),... ] + lbold = -1 + allnev = 0 + ic = 0 + ice = 0 + allic = 0 + lastElement = False + firstcall = True + + for ice,(lb,nev) in enumerate(result): + if ice == len(result): + lastElement = True + allnev += nev + if lb != lbold or lastElement: + if lbold != -1: + ic += 1 + allic += 1 + if allic < 101: + if ic == 9: + ic = 1 + lbevcount += '</tr><tr>' + lbevcount += '<td style="font-size:75%%">%i (%s)</td>' % (lbold,allnev) + else: + if firstcall: + lbevcount += '</tr><tr>' + lbevcount += '<td style="font-size:75%%" colspan="8">... <i>too many LBs (> 100) to show</i></td>' + firstcall = False + allnev = nev + lbold = lb + else: + allnev += nev + lbevcount += '</tr>' + run.stats['STR:'+s]['LBRecInfo'] = lbevcount + run.stats['STR:'+s]['LBRecInfo'] = result + + # add overlap information to the run stats + for i in xrange(nst): + statkey = 'STR:'+streams[i] + run.stats[statkey]['StrOverlap'] = [] + denom = stovmat.getitem(i,i) + if denom==0: continue + for j in xrange(nst): + if i == j or stovmat.getitem(i,j) == 0: continue + fraction = 100 + if stovmat.getitem(i,j) != denom: + fraction = float(stovmat.getitem(i,j))/float(denom)*100.0 + run.stats[statkey]['StrOverlap'] += [(streams[j], fraction)] + + + + # selection... + if not self.passes(zip(streams,strevents),0): continue + newrunlist += [run.runNr] + allStreams.update(streams) + for k,v,s in zip(streams,strevents,strsize): + run.addResult('STR:'+k, (v,s)) + + + + + allStreams = ['STR:'+s for s in allStreams] + allStreams.sort(lambda x,y: 2*cmp(y[4],x[4]) + cmp(x[5:],y[5:])) + + # fill the gaps + for run in runlist: + for s in allStreams: + if not s in run.result: + run.addResult(s, 'n.a.') + + runlist = [r for r in runlist if r.runNr in newrunlist] + + # check if the streams in 'allStreams' match the show patterns + for s in allStreams: + if any( [p.match(s[4:])!=None for p in compiledShowPatterns] ): + Run.AddToShowOrder(DataKey(s, keytype=DataKey.STREAM)) + + + + + if useTier0: + + # retrieve Tier-0 information + from CoolRunQuery.AtlRunQueryTier0 import GetTier0_datasetsAndTypes + tier0connection = coolDbConn.GetTier0DBConnection() + cursor = tier0connection.cursor() + cursor.arraysize = 1000 + tier0retdico = GetTier0_datasetsAndTypes( cursor, runnrlist ) + + # add Tier0 information + for run in runlist: + for s in allStreams: + if run.result[s]=='n.a.': continue + run.stats[s]['StrTier0TypesRAW'] = {} + run.stats[s]['StrTier0TypesESD'] = {} + run.stats[s]['StrTier0AMI'] = {} + if run.runNr in tier0retdico.keys(): + for dsname,t,pstates in tier0retdico[run.runNr]: + if s.replace('STR:','') in dsname: + if '.RAW' in dsname: + if '.merge' in dsname: + prodstep = 'StrTier0TypesESD' + t = '(RAW)' + else: + prodstep = 'StrTier0TypesRAW' + t = '(RAW)' + else: + if '.recon.' in dsname: + prodstep = 'StrTier0TypesRAW' + else: + prodstep = 'StrTier0TypesESD' + if not run.stats[s]['StrTier0AMI'].has_key( prodstep ): + dsnamesplit = dsname.split('.') + if len(dsnamesplit)>5: + amitag = dsnamesplit[5] + run.stats[s]['StrTier0AMI'][prodstep] = amitag + else: + amitag = '' + # check if on CAF + oncaf = False + if pstates and 'replicate:done' in pstates: oncaf = True + + # fill the run stats + if not run.stats[s][prodstep].has_key(t): run.stats[s][prodstep][t] = oncaf + + + # Done + duration = time() - start + if len(self.streams)!=0: print " ==> %i runs found (%.2f sec)" % (len(runlist),duration) + else: print " ==> Done (%g sec)" % duration + return runlist + + def passes(self,streamevents,key): + # streamevents is [('physics_L1Calo', 87274, False), ('physics_RPCwBeam', 1075460, True), ('debug_hlterror', 151, False),...] + for streampattern, neventreq, negate, pattern in self.selstreamspatterns: + nevents = 0 + foundmatchingStream = False + for se in streamevents: + if streampattern.match(se[0]): + nevents += se[1] + foundmatchingStream = True + if neventreq: + if neventreq[-1] in '+-': + if neventreq[-1] == '-': passreq = nevents<int(req[1][:-1]) + else: passreq = nevents>int(neventreq[:-1]) + else: + passreq = nevents>int(neventreq) + else: + if negate: + passreq = not foundmatchingStream + else: + passreq = foundmatchingStream + if not passreq: + return False + return True + diff --git a/Database/CoolRunQuery/python/selector/AtlRunQuerySelectorTrigger.py b/Database/CoolRunQuery/python/selector/AtlRunQuerySelectorTrigger.py new file mode 100644 index 00000000000..28e53b85fb6 --- /dev/null +++ b/Database/CoolRunQuery/python/selector/AtlRunQuerySelectorTrigger.py @@ -0,0 +1,588 @@ +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + +"""Provides Trigger related selectors: + +TriggerSelector Trigger menu from TriggerDB +TrigKeySelector SMK +BGSKeySelector BGK +L1TrigKeySelector L1PSK +HLTTrigKeySelector HLTPSK +RatesSelector L1 rates from COOL + +""" + +import re,sys + +from time import time,strftime,gmtime +from collections import defaultdict + +from PyCool import cool + +from CoolRunQuery.selector.AtlRunQuerySelectorBase import Selector, RunLBBasedCondition +from CoolRunQuery.utils.AtlRunQueryUtils import coolDbConn, GetRanges, SmartRangeCalulator + + +class TrigKeySelector(RunLBBasedCondition): + def __init__(self, name): + self.showRelease = False + self.showTrigKeys = False + self.smkCutRange = None + self.relCutRange = None + self.trigkeys = None + self.release = None + super(TrigKeySelector,self).__init__(name=name, + dbfolderkey='COOLONL_TRIGGER::/TRIGGER/HLT/HltConfigKeys', + channelKeys = [(0,'SMK','MasterConfigurationKey'), + (0,'Release','ConfigSource')]) + self.applySelection = False + + def setSelectSMK(self,smks): + self.trigkeys = smks + if self.trigkeys: + self.smkCutRange = GetRanges(self.trigkeys) + self.applySelection = True + + def setSelectRelease(self,release): + self.release = release + if self.release: + self.relCutRange = GetRanges(','.join(self.release), intRepFnc=self.relStringToInt) + self.applySelection = True + + def setShow(self, what): + if what == 'smk': + super(TrigKeySelector,self).setShowOutput('SMK') + if what == 'release': + super(TrigKeySelector,self).setShowOutput('Release') + + def relStringToInt(self, r): + major, minor, sub, patch = ([int(x) for x in r.split('.')] + [0,0,0,0])[0:4] + if sub<10: sub*=10; + return 1000000*major+10000*minor+100*sub+patch + + def __str__(self): + if self.applySelection: + if self.trigkeys and self.release: + return 'SELOUT Checking if SMK matches "%s" and release matches "%s"' % (self.trigkeys,self.release) + elif self.trigkeys: + return 'SELOUT Checking if SMK matches "%s"' % self.trigkeys + else: + return 'SELOUT Checking if release matches "%s"' % (self.release) + else: + if self.showTrigKeys and self.showRelease: + return "Retrieving SMK and release version" + elif self.showTrigKeys: + return "Retrieving SMK" + else: + return "Retrieving release version" + + def passes(self,values,key): + if self.smkCutRange: + smkPass = False + try: val = int(values) + except ValueError: + self.selDataMissing = True + smkPass = True + else: + for cr in self.smkCutRange: + if val>=cr[0] and val<=cr[1]: smkPass= True + if not smkPass: return False + if self.relCutRange: + relPass = False + val = values[1] + if val=='n.a.': + self.selDataMissing = True + relPass = True + else: + val = val.split(',') + if len(val)>1: + val = self.relStringToInt(val[1]) + for cr in self.relCutRange: + if val>=cr[0] and val<=cr[1]: relPass= True + else: + self.selDataMissing = True + relPass = True + if not relPass: return False + return True + + def prettyValue(self, value, key): + if key=='Release' and value!="n.a." and ',' in value: + return value.split(',')[1] + return value + + def runAfterQuery(self,runlist): + for k in self.ResultKey(): + if k != 'SMK': continue + smks = set() + for run in runlist: + smk = run.result['SMK'] + if not str.isdigit(smk): continue + smks.add(int(smk)) + + from CoolRunQuery.utils.AtlRunQueryTriggerUtils import getSmkNames, getRandom + smknames = getSmkNames(smks) + + for run in runlist: + smk = run.result['SMK'] + info = list(smknames[int(smk)] if str.isdigit(smk) else ("","","")) + if info[2]=="" or info[2]=="~": info[2]="no comment" + run.stats[k] = { "info" : tuple(info), + "random" : getRandom(smk) } + + +class BGSKeySelector(RunLBBasedCondition): + def __init__(self, name): + self.showBGKeys = False + super(BGSKeySelector,self).__init__(name=name, + dbfolderkey='COOLONL_TRIGGER::/TRIGGER/LVL1/BunchGroupKey', + channelKeys = [(0,'BGS Key','Lvl1BunchGroupConfigurationKey')]) + def __str__(self): + return "Retrieving Bunch group set key" + + def passes(self,values,key): + return True + + def prettyValue(self, value, key): + if not str.isdigit(value): return None + return int(value) + + def runAfterQuery(self,runlist): + for k in self.ResultKey(): + for run in runlist: + run.stats[k] = { "blocks" : [], "first" : 0 } + entries = run.data[k] + if len(entries)==0: continue + blocks = [] + for entry in entries: + v = entry.value + if len(blocks) > 0 and blocks[-1][0]==v and blocks[-1][2]==entry.startlb: + blocks[-1][2] = entry.endlb + else: + blocks += [ [v, entry.startlb, entry.endlb] ] + run.stats[k] = { "blocks" : blocks, "first" : entries[0].value } + + +class L1TrigKeySelector(RunLBBasedCondition): + def __init__(self, name): + self.showTrigKeys = False + super(L1TrigKeySelector,self).__init__(name=name, + dbfolderkey='COOLONL_TRIGGER::/TRIGGER/LVL1/Lvl1ConfigKey', + channelKeys = [(0,'L1 PSK','Lvl1PrescaleConfigurationKey')]) + + + def __str__(self): + return "Retrieving L1 PSK" + + def passes(self,values,key): + return True + + def prettyValue(self, value, key): + return int(value) if str.isdigit(value) else None + + def runAfterQuery(self,runlist): + for k in self.ResultKey(): + for run in runlist: + run.stats[k] = { "blocks" : [], "first" : 0 } + entries = run.data[k] + print "OOOOOOOO",entries + if len(entries)==0: continue + blocks = [] + for entry in entries: + v = entry.value + if v == "n.a.": v = None + if len(blocks) > 0 and blocks[-1][0]==v and blocks[-1][2]==entry.startlb: + blocks[-1][2] = entry.endlb + else: + blocks += [ [v, entry.startlb, entry.endlb] ] + run.stats[k] = { "blocks" : blocks, "first" : entries[0].value } + HLTTrigKeySelector.combineHltL1Keys(runlist) + + + + +class HLTTrigKeySelector(RunLBBasedCondition): + def __init__(self, name): + self.showTrigKeys = False + if Selector.db == 'OFLP200': + super(HLTTrigKeySelector,self).__init__(name=name, + dbfolderkey='COOLONL_TRIGGER::/TRIGGER/HLT/HltConfigKeys', + channelKeys = [(0,'HLT PSK','HltPrescaleConfigurationKey')]) + else: + super(HLTTrigKeySelector,self).__init__(name=name, + dbfolderkey='COOLONL_TRIGGER::/TRIGGER/HLT/PrescaleKey', + channelKeys = [(0,'HLT PSK','HltPrescaleKey')]) + + def __str__(self): + return "Retrieving HLT PSK" + + def passes(self,values,key): + return True + + def prettyValue(self, value, key): + if not str.isdigit(value): return None + return int(value) + + def runAfterQuery(self,runlist): + for k in self.ResultKey(): + for run in runlist: + blocks = [] + run.stats[k] = {} + for entry in run.data[k]: + v = entry.value + if v == "n.a.": v = None + if len(blocks) > 0 and blocks[-1][0]==v and blocks[-1][2]==entry.startlb: + blocks[-1][2] = entry.endlb + else: + blocks += [ [v, entry.startlb, entry.endlb] ] + run.stats[k] = { "blocks" : blocks, "first" : run.data[k][0].value } + + HLTTrigKeySelector.combineHltL1Keys(runlist) + + @classmethod + def combineHltL1Keys(cls, runlist): + l1keys = set() + hltkeys = set() + for run in runlist: + if 'L1 PSK' in run.stats: + l1keys.update( [x[0] for x in run.stats['L1 PSK']['blocks']] ) + if 'HLT PSK' in run.stats: + hltkeys.update( [x[0] for x in run.stats['HLT PSK']['blocks']] ) + + from CoolRunQuery.utils.AtlRunQueryTriggerUtils import getL1PskNames, getHLTPskNames + if len(l1keys)>0 and len(hltkeys)>0: + l1names = getL1PskNames(l1keys) + hltnames = getHLTPskNames(hltkeys) + + for run in runlist: + if not ('L1 PSK' in run.stats and 'HLT PSK' in run.stats): continue + ic = 0 + blocks = set() + for l1key, l1beg, l1end in run.stats['L1 PSK' ]['blocks']: + for hltkey, hltbeg, hltend in run.stats['HLT PSK' ]['blocks']: + lbmin = max(ic,min(l1beg,hltbeg)) + lbmax = min(l1end-1,hltend-1) + if lbmin > lbmax or lbmin == 0 or lbmax == 0: continue + ic = lbmax + 1 + # accept if new + l1name = l1names[l1key] if l1key else "" + hltname = hltnames[hltkey] if hltkey else "" + elm = (lbmin, lbmax, l1key, hltkey, l1name, hltname) + blocks.update([elm]) + run.stats['PSK' ] = {'blocks' : sorted(list(blocks)) } + + + +class RatesSelector(RunLBBasedCondition): + def __init__(self, name, trigger=[]): + # trigger can be ["*Electron*", "L1_*"] + # rate will be printed for all triggers matching the pattern + + super(RatesSelector,self).__init__(name=name, + dbfolderkey='COOLONL_TRIGGER::/TRIGGER/LUMI/LVL1COUNTERS', + channelKeys = [(-1,'TriggerRates','TriggerName')]) + + self.trigger=trigger + from CoolRunQuery.AtlRunQuerySelectorWorker import SelectorWorker + self.trigSel = SelectorWorker.getRetrieveSelector('trigger','TriggerSelector') + if len(trigger)>0: + self.trigSel.addShowTriggerPattern(','.join(trigger)) + + def addPattern(self,pattern): + if pattern=="" or pattern=="L1": + pattern="L1_EM5,L1_TAU5,L1_XE10,L1_J5,L1_MBTS_4_4,L1_MU6" + self.trigSel.addShowTriggerPattern(pattern) + self.trigger += pattern.split(',') + + + def __str__(self): + return "Retrieving trigger rates for %r" % ','.join(self.trigger) + + def select(self, runlist): + start = time() + print self, + sys.stdout.flush() + + fL1R = coolDbConn.GetDBConn(self.schema,db=self.db).getFolder(self.folder) + + for r in runlist: + menu = r.result['TriggerMenu'] + + namelookup = 256*[''] + channellist = [] + #print menu + for tr in menu: + if not tr.name.startswith("L1_"): continue + ch = tr.counter + namelookup[ch] = tr.name + channellist.append(ch) + + if len(channellist)>50: + r.addResult(self.ResultKey()[0], "Too many L1 triggers requested (%i), maximum is 50" % len(channellist)) + continue + + if len(channellist)==0: + r.addResult(self.ResultKey()[0], "No match") + continue + + channellist.sort() + ch = channellist[0] + chanselL1 = cool.ChannelSelection(ch,ch,cool.ChannelSelection.sinceBeforeChannel) + for ch in channellist[1:]: + chanselL1.addChannel(ch) + + iovmin=(r.runNr << 32)+0 + iovmax=((r.runNr+1) << 32)-1 + + countsholder = defaultdict(list) + + # the hlt part + objs = fL1R.browseObjects( iovmin, iovmax, chanselL1) + while objs.goToNext(): + obj=objs.currentRef() + ch = obj.channelId() + countsholder[namelookup[ch]].append((obj.since()&0xFFFFFFFF, + obj.payloadValue('BeforePrescale'), + obj.payloadValue('AfterPrescale'), + obj.payloadValue('L1Accept'))) + + r.addResult(self.ResultKey()[0], countsholder) + + duration = time() - start + + if self.applySelection: print " ==> %i runs found (%.2f sec)" % (len(runlist),duration) + else: print " ==> Done (%g sec)" % duration + + return runlist + +class TriggerSelector(RunLBBasedCondition): + + def __init__(self, name, trigger=[]): + # trigger can be ["*Electron*", "L1_*"] + # will select runs that satiesfy an 'or' of all patterns in the list + # a pattern without number are checked for existence + # a pattern with number will be checked against the sum of the events in all matching triggers + from CoolRunQuery.AtlRunQuerySelectorWorker import SelectorWorker + SelectorWorker.getRetrieveSelector('trigkey','TrigKeySelector') + SelectorWorker.getRetrieveSelector('l1trigkey','L1TrigKeySelector') + SelectorWorker.getRetrieveSelector('hlttrigkey','HLTTrigKeySelector') + + super(TriggerSelector,self).__init__(name=name, + dbfolderkey='COOLONL_TRIGGER::/TRIGGER/HLT/Menu', + channelKeys = [(-1,'TriggerMenu','ChainName')]) + + self.triggers = ' or '.join(trigger) + self.applySelection = (len(trigger)>0) + self.seltriggerpatterns = [] + self.showtriggerpatterns = [] + + for s in trigger: + s += "$" + p = re.compile(s.split()[0].replace(',','|').replace('*','.*').replace('?','.').replace('%','.*'), re.I) + self.seltriggerpatterns += [p] + + + def addShowTriggerPattern(self, triggerpattern): + if triggerpattern=="": triggerpattern = "*" + self.showtriggerpatterns += triggerpattern.split(',') + + def __str__(self): + if self.applySelection: return 'SELOUT Checking if the trigger name matches "%s"' % self.triggers + else: return "Retrieving trigger names [%s]" % ','.join(self.showtriggerpatterns) + + + def getL1Prescales(self, l1psks): + from CoolRunQuery.utils.AtlRunQueryTriggerUtils import getL1Prescales + l1pscache = dict() + for l1psk in l1psks: + l1pscache[l1psk] = getL1Prescales(l1psk) + return l1pscache + + def getHLTPrescales(self, hltpsks): + from CoolRunQuery.utils.AtlRunQueryTriggerUtils import getHLTPrescales + + l2pscache = dict() + efpscache = dict() + + for hltpsk in hltpsks: + l2pscache[hltpsk], efpscache[hltpsk] = getHLTPrescales(hltpsk) + + return l2pscache, efpscache + + + def getMenuFromRun(self, smks): + from CoolRunQuery.utils.AtlRunQueryTriggerUtils import getL1Menu, getHLTMenu + + l1menucache = dict() + l2menucache = defaultdict(dict) + efmenucache = defaultdict(dict) + + # fill the menu and prescale caches + for smk in smks: + + # the l1 menu + l1items = getL1Menu(smk) + l1menucache[smk] = l1items + + # the hlt menu + l2chains, efchains = getHLTMenu(smk) + d2 = l2menucache[smk] = dict([(c.counter,c) for c in l2chains]) + d3 = efmenucache[smk] = dict([(c.counter,c) for c in efchains]) + + # connect the levels (assign lower counter) + for chain in efchains: + lowername = chain.lowername + if chain.lowername=="": continue + cc = chain.counter + if (cc in d2) and (chain.lowername == d2[cc].name): + chain.lower = d2[cc] + chain.lowercounter = cc + continue + for l2ch in l2chains: + if chain.lowername == l2ch.name: + chain.lower = l2ch + chain.lowercounter = l2ch.counter + break + + for chain in l2chains: + if chain.lowername == "": continue + for l1item in l1items: + if l1item and chain.lowername == l1item.name: + chain.lower = l1item + chain.lowercounter = l1item.counter + break + + # set display flag + for item in l1items + l2chains + efchains: + if item: + item.forshow = self.matchShowPattern(item.name) + + # set 'selected' flag + if self.applySelection: + for item in l1items + l2chains + efchains: + if item: + item.forselect = self.matchSelectPattern(item.name) + + return l1menucache, l2menucache, efmenucache + + + def select(self, runlist): + start = time() + # some preparation: compile the show patterns + self.compiledShowPatterns = [] + if '*' in self.showtriggerpatterns: + self.compiledShowPatterns = ['all'] + else: + for p in self.showtriggerpatterns: + p += "$" + self.compiledShowPatterns += [re.compile(p.replace('*','.*').replace('?','.').replace('%','.*'),re.I)] + + print self, + sys.stdout.flush() + newrunlist = [] + + runranges = SmartRangeCalulator(runlist) + + smks = [r.result['SMK'] for r in runlist] + l1psks = set() + hltpsks = set() + for r in runlist: + l1psks.update( [l1psk for (l1psk,firstlb,lastlb) in r.stats['L1 PSK']['blocks']] ) + hltpsks.update( [hltpsk for (hltpsk,firstlb,lastlb) in r.stats['HLT PSK']['blocks']] ) + + l1menucache, l2menucache, efmenucache = self.getMenuFromRun(smks) + + l1pscache = self.getL1Prescales(l1psks) + + l2pscache, efpscache = self.getHLTPrescales(hltpsks) + + for run in runlist: # go through old runlist and see + + smk = run.result['SMK'] + psks = [(x[2],x[3]) for x in run.stats['PSK']['blocks']] + + l1items = [it for it in l1menucache[smk] if it and (it.forshow or it.forselect)] + l2chains = [ch for ch in l2menucache[smk].values() if (ch.forshow or ch.forselect) and not ch.multiseeded] + efchains = [ch for ch in efmenucache[smk].values() if (ch.forshow or ch.forselect) and not ch.multiseeded and ch.lower and not ch.lower.multiseeded] + + value = defaultdict(list) + + for item in l1items: + for l1psk,hltpsk in psks: + if not l1psk: + value[item].append(None) + continue + itemps = l1pscache[l1psk][item.counter] + value[item].append(itemps if itemps>=0 else -1) + + for l2chain in l2chains: + for l1psk,hltpsk in psks: + if not l1psk or not hltpsk: + value[l2chain].append(None) + continue + chainps,chainpt = l2pscache[hltpsk][l2chain.counter] + if chainps<0: + value[l2chain].append(-1) + continue + chainps *= l1pscache[l1psk][l2chain.lowercounter] + value[l2chain].append(chainps if chainps>=0 else -1) + + for efchain in efchains: + l2chain = efchain.lower + for l1psk,hltpsk in psks: + if not l1psk or not hltpsk: + value[efchain].append(None) + continue + chainps,chainpt = efpscache[hltpsk][efchain.counter] + if chainps<0: + value[efchain].append(-1) + continue + l2chainps,l2chainpt = l2pscache[hltpsk][l2chain.counter] + chainps *= l2chainps + if chainps<0: + value[efchain].append(-1) + continue + chainps *= l1pscache[l1psk][l2chain.lowercounter] + value[efchain].append(chainps if chainps>=0 else -1) + + # remove all the disabled triggers + for tr,pslist in value.items(): + if not any([ps==None or ps>=0 for ps in pslist]): + value.pop(tr) + + if self.applySelection and not self.passes(value): continue + newrunlist += [run.runNr] + + run.addResult(self.ResultKey()[0], value) + + runlist = [r for r in runlist if r.runNr in newrunlist] + + duration = time() - start + + if self.applySelection: print " ==> %i runs found (%.2f sec)" % (len(runlist),duration) + else: print " ==> Done (%g sec)" % duration + + return runlist + + def matchShowPattern(self,name): + if len(self.compiledShowPatterns)==0 or self.compiledShowPatterns[0]=='all': + return True + else: + return any([p.match(name) for p in self.compiledShowPatterns]) + + + def matchSelectPattern(self,name): + return any([p.match(name) for p in self.seltriggerpatterns]) + + def prettyValue(self, value): + form = [] + for v in value: + if len(v)==2: + form += ["%s (%s)" % (v[0],','.join(v[1]))] + else: + form += ["%s (%s|%s)" % v] + return ', '.join(form) + + + def passes(self,triggers): + for chain in triggers: + if chain.forselect: return True + return False diff --git a/Database/CoolRunQuery/python/selector/__init__.py b/Database/CoolRunQuery/python/selector/__init__.py new file mode 100644 index 00000000000..74583d364ec --- /dev/null +++ b/Database/CoolRunQuery/python/selector/__init__.py @@ -0,0 +1,2 @@ +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + diff --git a/Database/CoolRunQuery/python/utils/AtlRunQueryCache.py b/Database/CoolRunQuery/python/utils/AtlRunQueryCache.py new file mode 100755 index 00000000000..b95d259d474 --- /dev/null +++ b/Database/CoolRunQuery/python/utils/AtlRunQueryCache.py @@ -0,0 +1,89 @@ +#!/bin/env python + +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + +from time import sleep +import pickle +import os + +DEBUG=False + +class Cache(object): + def __init__(self,cachedir,form): + self._cache = {} + self.cachedir = cachedir.rstrip('/') if os.path.exists(cachedir) else None + self.form = form + if self.cachedir: + print "Initializing the cache at",cachedir.rstrip('/') + + + def __call__(self, f, *args, **kwds): + if not self.cachedir: + return f # caching is automatically disabled if no cache directory exists + + print "Providing cache functionality for function '%s()'" % f.func_name + def newf(*args, **kwds): + key = kwds['cachekey'] + if not self.__is_cached(key): + self.__write_to_cache(key,f(*args, **kwds)) + else: + if DEBUG: + print "DEBUG: returning cached value for '%s'" % (self.form % key) + pass + return self._cache[key] + return newf + + def __name_pickle_cache(self,key): + filename = self.form % key + return '%s/%s.pickle' % (self.cachedir, filename) + + + def __write_to_cache(self,key, value): + # put into transient cache + self._cache[key] = value + + # store pickled version + pfname = self.__name_pickle_cache(key) + pf = open( pfname, 'w' ) + try: + pickle.dump(value, pf) + except: + print 'ERROR: could not write to cache: ' + pfname + sys.exit(1) + pf.close() + + def __is_cached(self,key): + # check transient cache + if key in self._cache: + return True + + # check for pickled version + pfname = self.__name_pickle_cache(key) + try: + pf = open( pfname, 'r' ) + except: + if DEBUG: + print 'DEBUG: could not read from cache: ' + pfname + return False + try: + self._cache[key] = pickle.load(pf) + except: + print "ERROR: could not unpickle '%s'" % pfname + sys.exit(1) + pf.close() + return True + + + + + +if __name__ == "__main__": + @Cache("/afs/cern.ch/user/s/stelzer/runsummary/cache/","simple_%i_second%i") + def aFunction(x,y,cachekey): + print "expensive" + return (3*x,y*y,x+y) + + x=13 + print aFunction(x,y=2,cachekey=(x,2)) + print aFunction(x,1,cachekey=(x,1)) + diff --git a/Database/CoolRunQuery/python/utils/AtlRunQueryDQUtils.py b/Database/CoolRunQuery/python/utils/AtlRunQueryDQUtils.py new file mode 100644 index 00000000000..71b15eaff73 --- /dev/null +++ b/Database/CoolRunQuery/python/utils/AtlRunQueryDQUtils.py @@ -0,0 +1,221 @@ +#!/bin/env python + +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration +# +# ---------------------------------------------------------------- +# Script : AtlRunQueryDQUtils.py +# Project: AtlRunQuery +# Purpose: Utility functions for AtlRunQuery +# Authors: Peter Onyisi (Chicago U) +# Created: May 6, 2010 +# ---------------------------------------------------------------- +# +from __future__ import division + +import sys, time +import xml.etree.cElementTree as et +import xmlrpclib +from PyCool import cool +dbSvc = cool.DatabaseSvcFactory.databaseService() + +from .AtlRunQuerySelectorBase import DataKey + +THIRTYTWOMASK=int(2**32-1) +SERVER='http://atlasdqm.cern.ch:8080' + +FLAGS_WE_CARE_ABOUT = ['PIXB', 'PIX0', 'PIXEA', 'PIXEC', + 'SCTB', 'SCTEA', 'SCTEC', + 'TRTB', 'TRTEA', 'TRTEC','TRTTR' + 'IDGL', 'IDAL', + 'EMBA', 'EMBC', 'EMECA', 'EMECC', 'HECA', 'HECC', + 'FCALA', 'FCALC', + 'TILBA', 'TILBC', 'TIEBA', 'TIEBC', + 'MBTSA', 'MBTSC', + 'MDTBA', 'MDTBC', 'MDTEA', 'MDTEC', + 'RPCBA', 'RPCBC', + 'TGCEA', 'TGCEC', + 'CSCEA', 'CSCEC', + 'LCD', + #'L1CAL', + #'L1MUB', 'L1MUE', + #'L1CTP', + #'TRCAL', 'TRBJT', 'TRBPH', 'TRCOS', + #'TRELE', 'TRGAM', 'TRJET', 'TRMET', + #'TRMBI', 'TRMUO', 'TRTAU', 'TRIDT', +# 'EIDB', 'EIDCR', 'EIDE', 'PIDB', 'PIDCR', 'PIDE', +# 'JETB', 'JETEA', 'JETEC', +# 'MET', 'METCALO', 'METMUON', +# 'TAUB', 'TAUCR', 'TAUE', + ] + +FLAGGROUPS = { + 'PIX': ['PIXB', 'PIX0', 'PIXEA', 'PIXEC'], + 'SCT': ['SCTB', 'SCTEA', 'SCTEC'], + 'TRT': ['TRTB', 'TRTEA', 'TRTEC', 'TRTTR'], + 'LAR': ['EMBA', 'EMBC', 'EMECA', 'EMECC', + 'HECA', 'HECC', 'FCALA', 'FCALC'], + 'EM': ['EMBA', 'EMBC', 'EMECA', 'EMECC'], + 'HEC': ['HECA', 'HECC'], + 'FCAL': ['FCALA', 'FCALC'], + 'TILE': ['TILBA', 'TILBC', 'TIEBA', 'TIEBC'], + 'MDT': ['MDTBA', 'MDTBC', 'MDTEA', 'MDTEC'], + 'RPC': ['RPCBA', 'RPCBC'], + 'TGC': ['TGCEA', 'TGCEC'], + 'CSC': ['CSCEA', 'CSCEC'], + 'MBTS': ['MBTSA', 'MBTSC'], + 'LUCID': ['LCD'] + } + +def _intersect(lbr1, lbr2): + def normalize(lbr): + return (max(1, lbr[0]), lbr[1] if lbr[1] != -1 else THIRTYTWOMASK) + tlbr1 = normalize(lbr1) + tlbr2 = normalize(lbr2) + rv = (max(tlbr1[0],tlbr2[0]), min(tlbr1[1],tlbr2[1])) + if rv[1] <= rv[0]: + return None + else: + return rv + +lumicache = {} + +def lumi(run, lbr): + if tuple(lbr) in lumicache: + return lumicache[tuple(lbr)] + + lblbdb = dbSvc.openDatabase('COOLONL_TRIGGER/COMP200', True) + lblb = lblbdb.getFolder('/TRIGGER/LUMI/LBLB') + lblestonl = lblbdb.getFolder('/TRIGGER/LUMI/LBLESTONL') + + lbs = lblb.browseObjects((run<<32)+lbr[0], + (run<<32)+lbr[1]-1, + cool.ChannelSelection(0)) + inf = {} + for obj in lbs: + lbpy = obj.payload() + #print (lbpy['EndTime']-lbpy['StartTime'])/1e9 + inf[(run, obj.since() & 0xFFFFFFFF)] = (lbpy['EndTime']-lbpy['StartTime'])/1e9 + if obj.since() & 0xFFFFFFFF == lbr[1]: + print 'Oops: this should not happen, appears to be off-by-one error' + lbls = lblestonl = lblestonl.browseObjects((run<<32)+lbr[0], + (run<<32)+lbr[1]-1, + cool.ChannelSelection(0)) + infl = {} + for obj in lbls: + lblpy = obj.payload() + infl[(run, obj.since() & 0xFFFFFFFF)] = lblpy['LBAvInstLumi'] + + #print sorted(infl.keys()) + totlum = 0 + for lb in inf: + if lb in infl: + totlum += inf[lb]*infl[lb] + else: + print 'Missing run %d, LB %d' % lb + lumicache[tuple(lbr)] = totlum + return totlum + +def GetDQEfficiency( rundict ): + + runset = set() + for runnum in rundict.keys(): runset.add(runnum) + + s = xmlrpclib.ServerProxy(SERVER) + flaginfo = s.get_dqmf_summary_flags_lb({'run_list': list(runset)}, FLAGS_WE_CARE_ABOUT, 'SHIFTOFL') + #print flaginfo + record = {} + for flag in FLAGS_WE_CARE_ABOUT: + record[flag] = [] + for run in runset: + if `run` not in flaginfo: + print '%s not in flaginfo' % run + del rundict[run] + continue + for sublb in rundict[run]: + for flag, periods in flaginfo[`run`].items(): + for period in periods: + ip = _intersect(period, sublb) + if ip is not None: + #print run, flag, ip, period[2] + #print lumi(run, ip) + record[flag].append((ip[1]-ip[0], period[2], lumi(run, ip))) + + + totallum = 0 + for run in rundict: + for pair in rundict[run]: + totallum += lumi(run, pair) + print 'Total lumi:', totallum + + flagsum = {} + + for flag in FLAGS_WE_CARE_ABOUT: + flagsum[flag] = {} + lr = record[flag] + cols = set([x[1] for x in lr]) + accounted = 0 + for col in cols: + llum = sum([x[2] for x in lr if x[1] == col]) + accounted += llum + print flag, col, llum, '%.2f%%' % (llum/totallum*100) + flagsum[flag][col] = (llum/totallum*100) + if abs(accounted-totallum) > 1e-8: + print flag, 'n.a.', totallum-accounted, '%.2f%%' % ((1-accounted/totallum)*100) + flagsum[flag]['n.a.'] = ((1-accounted/totallum)*100) + + def _sum(d1, d2): + rv = {} + for key in set(d1.keys() + d2.keys()): + rv[key] = d1.get(key, 0) + d2.get(key, 0) + #print rv + return rv + + for flagtop, flaggroup in FLAGGROUPS.items(): + vals = reduce(_sum, [flagsum[f] for f in flaggroup], {}) + print flagtop + for typ in vals: + print ' %s: %.2f%%' % (typ, vals[typ]/len(flaggroup)) + +def MakeDQeffHtml( dic, dicsum, datapath ): + s = '' + + + # run and time ranges + runs = dic[DataKey('Run')] + nruns = len(runs) + times = dic[DataKey('Start and endtime')] + + starttime = float(times[-1].partition(',')[0]) + endtime = float(times[0].partition(',')[2]) + + s = '<table style="color: #777777; font-size: 85%; margin-left: 14px" cellpadding="0" cellspacing="3">\n' + # runs in reverse order + s += '<tr><td><i>Number of runs selected:</i></td><td> %g</td><td></td></tr>\n' % (len(runs)) + s += '<tr><td><i>First run selected:</i></td><td> %s</td><td> (%s)</td></tr>\n' % (runs[-1], time.strftime("%a %b %d, %Y, %X",time.gmtime(starttime))) + s += '<tr><td><i>Last run selected:</i></td><td> %s</td><td> (%s)</td></tr>\n' % (runs[0], time.strftime("%a %b %d, %Y, %X",time.gmtime(endtime))) + s += '</table>\n' + + # -------------------------------------------------------------------------------------- + # run summary table + s += '<p></p>\n' + s += '<table style="margin-left: 14px">\n' + s += '<tr><td><b>Run / Event Summary</b></td></tr>\n' + s += '</table>\n' + + return s + +if __name__ == '__main__': + + p = et.parse(sys.argv[1]) + + rundict = {} + for lbc in p.getiterator('LumiBlockCollection'): + runnum = int(lbc.find('Run').text) + #print 'Run', runnum + runset.add(runnum) + rundict[runnum] = [] + for lbr in lbc.findall('LBRange'): + rundict[runnum].append([int(lbr.get('Start')), + int(lbr.get('End'))+1]) + #print ' LBs %s-%s' % (lbr.get('Start'), lbr.get('End')) + diff --git a/Database/CoolRunQuery/python/utils/AtlRunQueryFastBlobRead.py b/Database/CoolRunQuery/python/utils/AtlRunQueryFastBlobRead.py new file mode 100644 index 00000000000..2d05efa3705 --- /dev/null +++ b/Database/CoolRunQuery/python/utils/AtlRunQueryFastBlobRead.py @@ -0,0 +1,21 @@ +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + + +import PyCool # need to import this, so that later imports don't override blob_read +import PyCintex +PyCintex.gbl.cool.IDatabase # force the load of the dictionary (to stay on the safe side) +from PyCintex import gbl, getAllClasses + +def blob_read(self, size = -1): + if size < 0: + endpos = self.size() + else: + endpos = self.pos + size + beginpos = self.pos + self.pos = endpos + buf = self.startingAddress() + buf.SetSize(self.size()) + return buf[beginpos:endpos] + +# add the new functions +getattr(gbl,"coral::Blob").read = blob_read diff --git a/Database/CoolRunQuery/python/utils/AtlRunQueryIOV.py b/Database/CoolRunQuery/python/utils/AtlRunQueryIOV.py new file mode 100644 index 00000000000..59b1e9a778e --- /dev/null +++ b/Database/CoolRunQuery/python/utils/AtlRunQueryIOV.py @@ -0,0 +1,118 @@ +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + + +import sys,os,copy, time + +from PyCool import cool + +class IOVTime: + def __init__(self, run=None, lb=None, timerunlb=None,timebased=False): + if isinstance(run,IOVTime): + self.timebased = run.timebased + self.run = run.run + self.lb = run.lb + self.time = run.time + else: + self.timebased=timebased + self.run = self.lb = self.time = None + if timebased: + self.time=timerunlb + else: + if timerunlb!=None: + self.run = timerunlb>>32 + self.lb = timerunlb&0xFFFFFFFF + else: + self.run = run + self.lb = lb + + def __str__(self): + if self.timebased: + try: + return "%s" % time.strftime("%a %b %d %Y %X",time.gmtime(self.time/1E9)) + except: + return "infinity" + else: return "%i/%i" % (self.run,self.lb) + + def __lt__(self, other): + if self.timebased: return self.time<other.time + else: return self.run<other.run or self.run==other.run and self.lb<other.lb + + def __cmp__(self, other): + if self.timebased: + return cmp(self.time,other.time) + else: + if self.run==other.run: return cmp(self.lb,other.lb) + else: return cmp(self.run,other.run) + + def __eq__(self, other): + if self.timebased: return self.time==other.time + else: return self.run==other.run and self.lb==other.lb + + def __le__(self, other): + return self<other or self==other + + def __sub__(self, other): + if isinstance(other, int): + return IOVTime(timerunlb=self.timerunlb()-1) + elif isinstance(other, IOVTime): + return self.timerunlb()-other.timerunlb() + + + def timerunlb(self): + if self.timebased: + return self.time + res = long(self.run)<<32 + res += self.lb + return res + + def inRange(self,iovrange): + return iovrange.isInRange(self) + +class IOVRange: + def __init__(self, obj=None, starttime=None, endtime=None, runStart=None, lbStart=None, runEnd=None, lbEnd=None, timebased=False): + if isinstance(obj, cool.IObject): + self.startTime = IOVTime(timerunlb=obj.since(), timebased=timebased) + self.endTime = IOVTime(timerunlb=obj.until(), timebased=timebased) + elif isinstance(obj, IOVRange): + self.startTime = obj.startTime + self.endTime = obj.endTime + elif isinstance(starttime, IOVTime) and isinstance(endtime, IOVTime): + self.startTime = starttime + self.endTime = endtime + elif isinstance(starttime,long) and isinstance(endtime,long): + self.startTime = IOVTime(timerunlb=starttime,timebased=timebased) + self.endTime = IOVTime(timerunlb=endtime,timebased=timebased) + elif runStart!=None and lbStart!=None and runEnd!=None and lbEnd!=None: + self.startTime = IOVTime(runStart, lbStart, timebased=False) + self.endTime = IOVTime(runEnd, lbEnd, timebased=False) + else: + print "IOVRange.__init__: Can't interpret arguments" + + def truncateToSingleRun(self,runnr): + if self.startTime.run<runnr: + self.startTime.run=runnr + self.startTime.lb=1 + if self.endTime.run>runnr: + self.endTime.run=runnr+1 + self.endTime.lb=0 + + def __str__(self): + return "[%s, %s)" % (self.startTime,self.endTime) + def __repr__(self): + return str(self) + + def __cmp__(self, other): + return cmp(self.startTime,other.startTime) + + def length(self): + return "%2.1f" % ((self.endTime-self.startTime)/1.E9) + + def isInRange(self, iovtime): + return self.startTime<=iovtime and iovtime<self.endTime + + def overlaps(self, o): + if o.startTime>=self.endTime or o.endTime<=self.startTime: + return False + return True + + diff --git a/Database/CoolRunQuery/python/utils/AtlRunQueryLookup.py b/Database/CoolRunQuery/python/utils/AtlRunQueryLookup.py new file mode 100644 index 00000000000..f09977ea688 --- /dev/null +++ b/Database/CoolRunQuery/python/utils/AtlRunQueryLookup.py @@ -0,0 +1,290 @@ +#!/bin/env python + +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration +# +# ---------------------------------------------------------------- +# Script : AtlRunQueryLookup.py +# Project: AtlRunQuery +# Purpose: Library with references for DQ flags and detector mask +# Authors: Andreas Hoecker (CERN), Joerg Stelzer (DESY) +# Created: Nov 13, 2008 +# ---------------------------------------------------------------- +# +# See: DetectorDescription/DetDescrCond/DetectorStatus/doc/mainpage.h +# Decription: +# http://alxr.usatlas.bnl.gov/lxr/source/atlas/DetectorDescription/DetDescrCond/DetectorStatus/doc/mainpage.h +# Implementation: +# http://alxr.usatlas.bnl.gov/lxr/source/atlas/DetectorDescription/DetDescrCond/DetectorStatus/python/DetStatusLib.py + +DQChannelDict = {'PIXB':101,'PIX0':102,'PIXEA':104,'PIXEC':105, + 'SCTB':111,'SCTEA':114,'SCTEC':115, + 'TRTB':121,'TRTEA':124,'TRTEC':125,'TRTTR':126, + 'IDGL':130, # Obsolete: 'IDGB OBSOLETE':131,'IDGEA OBSOLETE':134,'IDGEC OBSOLETE':135, + 'IDAL':140, # Obsolete: 'IDAB OBSOLETE':141,'IDAEA OBSOLETE':144,'IDAEC OBSOLETE':145, + 'IDBS':150, + 'IDVX':161, + 'IDBCM':170, + 'EMBA':202,'EMBC':203,'EMECA':204,'EMECC':205, + 'HECA':214,'HECC':215, + 'FCALA':224,'FCALC':225, + 'TIGB':230, + 'TILBA':232,'TILBC':233,'TIEBA':234,'TIEBC':235, + 'MBTSA':244,'MBTSC':245, + 'CALBA':251,'CALEA':254,'CALEC':255, + 'MDTBA':302,'MDTBC':303,'MDTEA':304,'MDTEC':305, + 'RPCBA':312,'RPCBC':313, + 'TGCEA':324,'TGCEC':325, + 'CSCEA':334,'CSCEC':335, + 'LCDA':353,'LCDC':354, + 'ALFA':360,'ZDC':370, + 'L1CAL':401,'L1MUB':402,'L1MUE':403,'L1CTP':404, + 'TRCAL':411, + 'TRBJT':421,'TRBPH':422,'TRCOS':423,'TRELE':424, + 'TRGAM':425,'TRJET':426,'TRMET':427,'TRMBI':428, + 'TRMUO':429,'TRTAU':430,'TRIDT':431, + 'LUMI':450, 'LUMIONL':451, + 'RUNCLT':460, + 'RCOPS':461, + 'ATLGL':480,'ATLSOL':481,'ATLTOR':482, + 'EIDB':501,'EIDCR':502,'EIDE':503, + 'PIDB':505,'PIDCR':506,'PIDE':507, + 'EIDF':508,'EIDSOFT':509, + 'MSTACO':510,'MMUIDCB':511,'MMUIDVX':512, + 'MMUGIRL':513,'MMUBOY':514,'MMUIDSA':515, + 'MMUTAG':516,'MMTIMO':517,'MCMUTAG':518, + 'MCALLHR':519, + 'JETB':521,'JETEA':524,'JETEC':525, + 'JETFA':526,'JETFC':527, + 'METCALO':531,'METMUON':532, + 'BTGLIFE':541,'BTGSOFTE':544,'BTGSOFTM':545, + 'TAUB':551,'TAUCR':552,'TAUE':553} + +DQSuperGroupsDict = {1 : ['Inner detector', ['PIXB','PIX0','PIXEA','PIXEC','SCTB','SCTEA','SCTEC','TRTB','TRTEA','TRTEC','TRTTR', + 'IDGL','IDAL','IDBS','IDVX']], + 2 : ['Calorimeter', ['EMBA','EMBC','EMECA','EMECC','HECA','HECC','FCALA','FCALC', + 'TIGB','TILBA','TILBC','TIEBA','TIEBC','CALBA','CALEA','CALEC']], + 3 : ['Muon systems', ['MDTBA','MDTBC','MDTEA','MDTEC','RPCBA','RPCBC','TGCEA','TGCEC','CSCEA','CSCEC']], + 4 : ['Forward detectors and luminosity', ['MBTSA','MBTSC','IDBCM','LCDA','LCDC','ALFA','ZDC','LUMI']], + 5 : ['Global, Magnets, DAQ and Trigger',['ATLGL','ATLSOL','ATLTOR','RUNCLT','RCOPS', + 'L1CAL','L1MUB','L1MUE','L1CTP', + 'TRCAL','TRBJT','TRBPH','TRCOS','TRELE','TRGAM','TRJET','TRMET','TRMBI','TRMUO','TRTAU','TRIDT']], + 6 : ['Combined performance', ['EIDB','EIDCR','EIDE', + 'PIDB','PIDCR','PIDE', + 'EIDF','EIDSOFT', + 'MSTACO','MMUIDCB','MMUIDVX', + 'MMUGIRL','MMUBOY','MMUIDSA', + 'MMUTAG','MMTIMO','MCMUTAG', + 'MCALLHR', + 'JETB','JETEA','JETEC','JETFA','JETFC', + 'METCALO','METMUON', + 'BTGLIFE','BTGSOFTE','BTGSOFTM', + 'TAUB','TAUCR','TAUE']]} + +DQGroupDict = { 100:'PIX', + 110:'SCT', + 120:'TRT', + 130:'IDGL', + 140:'IDAL', + 150:'IDBS', + 160:'IDVX', + 170:'IDBCM', + 200:'LArEM', + 210:'HEC', + 220:'FCAL', + 230:'TILE', + 240:'MBTS', + 250:'CAL', + 300:'MDT', + 310:'RPC', + 320:'TGC', + 330:'CSC', + 350:'LCD', + 360:'ALFA', + 370:'ZDC', + 400:'L1', + 410:'TRIG', + 420:'TRBJT', + 450:'LUMI', + 460:'RUNCLT', + 500:'EGAMCP', + 510:'MUONCP', + 520:'JETCP', + 530:'METCP', + 540:'BTAGCP', + 550:'TAUCP'} + +def DQChannels(): + # sort disctionary + dqitems = DQChannelDict.items() + dqitems = [(v, k) for (k, v) in dqitems] + dqitems.sort() + return dqitems + +def DQChannel(name): + name = name.upper() + if ':' in name: name = name[name.index(':')+1:] + # OLD: if name.startswith('CP_'): return -1 + if '_' in name: return -1 + return DQChannelDict[name] + + +def isDQ(name): + name = (name.split(':')[-1].split('#')[0]).upper() + if name.startswith("CP_") or name.startswith("PHYS_") or name.startswith("TRIG_") or name.startswith("LUM_") or name.startswith("GLOBAL_"): return True + return name in DQChannelDict + + +OLCAlgorithms = { 0: 'ATLAS_PREFERRED', + 1: 'LHC', + 101: 'LUCID_ZEROS_OR', + 102: 'LUCID_ZEROS_AND', + 103: 'LUCID_HITS_OR', + 104: 'LUCID_HITS_AND', + 151: 'LUCID_AND', + 201: 'BCM_H_ZEROS_AND', + 202: 'BCM_H_EVENTS_AND', + 203: 'BCM_H_EVENTS_XORA', + 204: 'BCM_H_EVENTS_XORC', + 205: 'BCM_V_ZEROS_AND', + 206: 'BCM_V_EVENTS_AND', + 207: 'BCM_V_EVENTS_XORA', + 208: 'BCM_V_EVENTS_XORC', + 301: 'MBTS_ZEROS_AND', + 302: 'MBTS_ZEROS_OR', + 303: 'MBTS_HITS_AND', + 304: 'MBTS_HITS_OR', + 401: 'ZDC', + 501: 'FCAL', + 601: 'HLT', + 901: 'OffLumi_LArTime_Events', + 998: 'OflLumi_Fake0', + 999: 'OflLumi_Fake1' } + +def InitDetectorMaskDecoder(): + + # taken from: + # http://alxr.usatlas.bnl.gov/lxr/source/tdaq-common/eformat/src/DetectorMask.cxx + # + # note that these here are obsolete: + # https://svnweb.cern.ch/trac/atlastdaq/browser/DAQ/online/RCUtils/trunk/src/get_detectormask.cc + # http://isscvs.cern.ch/cgi-bin/viewcvs-all.cgi/DAQ/online/RunControl/src/get_detectormask.cc?root=atlastdaq&view=markup + + dName = ['unknown']*64 + + dName[0] = "Pix Barrel" + dName[1] = "Pix Disks" + dName[2] = "Pix B-Layer" + dName[3] = "REMOVED" + + dName[4] = "SCT BA" + dName[5] = "SCT BC" + dName[6] = "SCT EA" + dName[7] = "SCT EC" + + dName[8] = "TRT BA" + dName[9] = "TRT BC" + dName[10] = "TRT EA" + dName[11] = "TRT EC" + + dName[12] = "LAr EMBA" + dName[13] = "LAr EMBC" + dName[14] = "LAr EMECA" + dName[15] = "LAr EMECC" + dName[16] = "LAr HECA" + dName[17] = "LAr HECC" + dName[18] = "LAr FCALA" + dName[19] = "LAr FCALC" + + dName[20] = "Tile BA" + dName[21] = "Tile BC" + dName[22] = "Tile EA" + dName[23] = "Tile EC" + + dName[24] = "MDT BA" + dName[25] = "MDT BC" + dName[26] = "MDT EA" + dName[27] = "MDT EC" + dName[28] = "RPC BA" + dName[29] = "RPC BC" + dName[30] = "TGC EA" + dName[31] = "TGC EC" + + dName[32] = "CSC EA" + dName[33] = "CSC EC" + + dName[34] = "L1Calo preprocessor" + dName[35] = "L1Calo cluster DAQ" + dName[36] = "L1Calo cluster RoI" + dName[37] = "L1Calo Jet/E DAQ" + dName[38] = "L1Calo Jet/E RoI" + dName[39] = "MUCTPI" + dName[40] = "CTP" + dName[41] = "L2SV" + dName[42] = "SFI" + dName[43] = "SFO" + dName[44] = "LVL2" + dName[45] = "EF" + + dName[46] = "BCM" + dName[47] = "Lucid" + dName[48] = "ZDC" + dName[49] = "Alfa" + + dName[50] = "TRT_ANCILLARY_CRATE" + dName[51] = "TILECAL_LASER_CRATE" + dName[52] = "MUON_ANCILLARY_CRATE" + dName[53] = "TDAQ_BEAM_CRATE" + + # list of detector NOT part of 'all' tag (means, they can be in, but are not required) + notInAll = ['CSC', 'L2SV', 'SFI', 'SFO', 'LVL2', 'EF', 'Lucid', 'ZDC', 'Alfa', + 'TRT_ANCILLARY_CRATE','TILECAL_LASER_CRATE','MUON_ANCILLARY_CRATE','TDAQ_BEAM_CRATE' ] + + NotInAll = ['']*64 + for i in range(0,len(dName)): + for n in notInAll: + if dName[i] == n: NotInAll[i] = ' NotInAll' + + vetoedbits = [3, 50, 51, 52, 53] + [x for x in range(54,64)] + + return (dName, NotInAll, vetoedbits) + +def DecodeDetectorMask( mask, smart=False ): + dName, NotInAll, vetoedbits = InitDetectorMaskDecoder() + col = '#000000' + if smart: col = '#106734' + res = '<b>Detector mask = %i, corresponding to the systems:</b><br><font color="%s">' % (mask,col) + ic = 0 + found = False + for i in range(64): + if (mask & (1 << i)): + found = True + res += dName[i] + ", " + ic += 1 + if smart: + # check if it is more interesting to give the missing systems + if ic > 30: + res = '<b>Detector mask = %i.<br> <font color="#aa0000">The following systems are NOT in partition:</b><br>' % mask + for i in range(64): + if i not in vetoedbits and not (mask & (1 << i)): + found = True + res += dName[i] + ", " + + # chop off last comma-space + if found: + if len(res)>1: res = res[:-2] + res += '</font>' + else: + res += '???' + + return res + +def LArConfig(type): + # runtype, format + if 'runtype' in type.lower(): + return {0:'RawData', 1:'RawDataResult', 2:'Result'} + elif 'format' in type.lower(): + return {0:'Transparent', 1:'Format 1', 2:'Format 2'} + else: + print 'ERROR in LArconfig: unknown type %s' % type + sys.exit() + diff --git a/Database/CoolRunQuery/python/utils/AtlRunQueryMemUtil.py b/Database/CoolRunQuery/python/utils/AtlRunQueryMemUtil.py new file mode 100644 index 00000000000..f7da298fc68 --- /dev/null +++ b/Database/CoolRunQuery/python/utils/AtlRunQueryMemUtil.py @@ -0,0 +1,45 @@ +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + +import os + +_proc_status = '/proc/%d/status' % os.getpid() + +_scale = {'kB': 1024.0, 'mB': 1024.0*1024.0, + 'KB': 1024.0, 'MB': 1024.0*1024.0} + +def _VmB(VmKey): + '''Private. + ''' + global _proc_status, _scale + # get pseudo file /proc/<pid>/status + try: + t = open(_proc_status) + v = t.read() + t.close() + except: + return 0.0 # non-Linux? + # get VmKey line e.g. 'VmRSS: 9999 kB\n ...' + i = v.index(VmKey) + v = v[i:].split(None, 3) # whitespace + if len(v) < 3: + return 0.0 # invalid format? + # convert Vm value to bytes + return float(v[1]) * _scale[v[2]] + + +def memory(since=0.0): + '''Return memory usage in bytes. + ''' + return _VmB('VmSize:') - since + + +def resident(since=0.0): + '''Return resident memory usage in bytes. + ''' + return _VmB('VmRSS:') - since + + +def stacksize(since=0.0): + '''Return stack size in bytes. + ''' + return _VmB('VmStk:') - since diff --git a/Database/CoolRunQuery/python/utils/AtlRunQueryTimer.py b/Database/CoolRunQuery/python/utils/AtlRunQueryTimer.py new file mode 100644 index 00000000000..b91ebcedaa8 --- /dev/null +++ b/Database/CoolRunQuery/python/utils/AtlRunQueryTimer.py @@ -0,0 +1,104 @@ +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + + +from contextlib import contextmanager + +from time import time +from os import environ + +class TimeCount: + def __init__(self, name): + self.name = name + self.totaltime = 0 + self.subcounts = [] + + def __repr__(self): + return "%s: %f" % (self.name, self.totaltime) + + def __str__(self): + return "%s: %f" % (self.name, self.totaltime) + + def printRecursive(self, lvl): + from operator import attrgetter + # print myself + if self.subcounts: + # sum up sub counts + ts = sum([x.totaltime for x in self.subcounts]) + print "%s%-70s : %f (sub sum %f)" % (5*lvl*" ", self.name, self.totaltime, ts) + else: + print "%s%-70s : %f" % (5*lvl*" ", self.name, self.totaltime) + # sort sub counters + sortedByTime = sorted(self.subcounts,key=attrgetter('totaltime'),reverse=True) + # call print for sub counters + for subtc in sortedByTime: + subtc.printRecursive(lvl+1) + + def __cmp__(l, r): + return cmp(l.name,r) + + +class TimerStats: + level=0 + total = TimeCount('total') + totalFlat = {} + context = [] + + @classmethod + def saveTimeFlat(cls, exectime): + n = cls.context[-1] + if not n in cls.totalFlat: cls.totalFlat[n] = [0,0] + cls.totalFlat[n][0] += exectime + cls.totalFlat[n][1] += 1 + + + @classmethod + def saveTime(cls, exectime): + cur = cls.total # a TimeCount instance + for n in cls.context: + if n=='total': + cur = cls.total + continue + try: + idx = cur.subcounts.index(n) + cur = cur.subcounts[idx] + except: + cur.subcounts += [TimeCount(n)] + cur = cur.subcounts[-1] + cur.totaltime += exectime + cls.saveTimeFlat(exectime) + + @classmethod + def printTimeSummary(cls): + #import pickle + #f = open("timecount.pickle","w") + #pickle.dump(cls.total,f) + #f.close() + cls.total.printRecursive(0) + + @classmethod + def printTimeFlat(cls): + for name, [time, callcount] in sorted(cls.totalFlat.items(),key=lambda x: x[1][0]): + print "%-70s : %f (%i)" % (name, time, callcount) + + +@contextmanager +def timer(name): + "A context manager which spits out how long the block took to execute" + if False: #not environ.has_key("ARQDEBUG"): + yield + return + + from CoolRunQuery.utils.AtlRunQueryTimer import TimerStats as TS + + start = time() + TS.level += 1 + TS.context.append(name) + try: + yield + finally: + end = time() + execTime = end - start + TS.saveTime(execTime) + TS.level -= 1 + TS.context.pop() + print "%*s took %.2f sec to %s" % (5*TS.level, "...", execTime, name) diff --git a/Database/CoolRunQuery/python/utils/AtlRunQueryTriggerUtils.py b/Database/CoolRunQuery/python/utils/AtlRunQueryTriggerUtils.py new file mode 100755 index 00000000000..9df11eed5dc --- /dev/null +++ b/Database/CoolRunQuery/python/utils/AtlRunQueryTriggerUtils.py @@ -0,0 +1,285 @@ +#!/bin/env python + +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration +# +# ---------------------------------------------------------------- +# Script : AtlRunQueryTriggerUtils.py +# Project: AtlRunQuery +# Purpose: Trigger Utility functions for AtlRunQuery +# Authors: Andreas Hoecker (CERN), Joerg Stelzer (DESY) +# Created: Oct 27, 2010 +# ---------------------------------------------------------------- + +try: + set +except NameError: + from sets import Set + set = Set + +from TrigConfigSvc.TrigConfigSvcUtils import getTriggerDBCursor + +__cursor=None +__schema = "" + +class TriggerChain: + def __init__(self, name, counter, lowername="", lowercounter=-1, forshow=False, forselect=False, level=0): + self.name = name + self.counter = counter + self.lowername = "" if lowername=="~" else lowername + self.lowercounter = lowercounter + self.forshow = forshow + self.forselect = forselect + self.multiseeded = (self.lowername=="") or (',' in self.lowername) + self.level = level + self.lower = None + + def __repr__(self): + return "TC:"+self.name + + + + +def getUsedTables(output, condition, schemaname, tables): + schemaname = schemaname.rstrip('.')+'.' + usedtables = set() + for o in output: + usedtables.add(o.split('.')[0]) + for c in condition: + for p in c.split(): + if '.' in p and not '\'' in p: usedtables.add(p.split('.')[0]) + return ["%s%s %s" % (schemaname,tables[t],t) for t in usedtables] + + +def executeQuery(cursor, output, condition, schemaname, tables, bindvars=()): + query = 'select distinct %s from %s where %s' % \ + (', '.join(output), + ', '.join(getUsedTables(output, condition, schemaname, tables)), + ' and '.join(condition)) + if len(bindvars)==0: + cursor.execute(str(query)) + else: + cursor.execute(str(query),bindvars) + + return cursor.fetchall() + + +def getL1PskNames(psk): + global __cursor, __schema + if not __cursor: + __cursor,__schema = getTriggerDBCursor("TRIGGERDB") + + if type(psk) == set: psk = list(psk) + if type(psk) != list: psk = [psk] + + keyslist = ','.join([str(k) for k in psk if k]) + + if keyslist=="": + return {} + + tables = { 'L' : 'L1_PRESCALE_SET' } + + output = [ 'L.L1PS_ID', 'L.L1PS_NAME' ] + + condition = [ "L.L1PS_ID in (%s)" % keyslist ] + + res = executeQuery(__cursor, output, condition, __schema, tables) + + return dict(res) + + + +def getHLTPskNames(psk): + global __cursor, __schema + if not __cursor: + __cursor,__schema = getTriggerDBCursor("TRIGGERDB") + + if type(psk) == set: psk = list(psk) + if type(psk) != list: psk = [psk] + + if len(psk)==0: + return {} + + tables = { 'L' : 'HLT_PRESCALE_SET' } + + output = ['L.HPS_ID', 'L.HPS_NAME'] + + prescales = [str(k) for k in psk if k] + + if len(prescales)==0: return {} + + condition = [ "L.HPS_ID in (%s)" % ','.join(prescales) ] + + res = executeQuery(__cursor, output, condition, __schema, tables) + + return dict(res) + + +def getSmkNames(smk): + global __cursor, __schema + if not __cursor: + __cursor,__schema = getTriggerDBCursor("TRIGGERDB") + + if type(smk) == set: smk = list(smk) + if type(smk) != list: smk = [smk] + + if len(smk)==0: + return {} + + tables = { 'S' : 'SUPER_MASTER_TABLE' } + + output = ['S.SMT_ID', 'S.SMT_NAME', 'S.SMT_VERSION', 'S.SMT_COMMENT'] + + condition = [ "S.SMT_ID in (%s)" % ','.join([str(k) for k in smk]) ] + + res = executeQuery(__cursor, output, condition, __schema, tables) + + return dict([ (r[0],(r[1],r[2],r[3])) for r in res]) + +def getHLTMenu(smk): + global __cursor, __schema + if not __cursor: + __cursor,__schema = getTriggerDBCursor("TRIGGERDB") + + output = ['TC.HTC_NAME', 'TC.HTC_CHAIN_COUNTER', 'TC.HTC_LOWER_CHAIN_NAME', 'TC.HTC_L2_OR_EF' ] + + tables = {} + tables['SM'] = 'SUPER_MASTER_TABLE' + tables['HM'] = 'HLT_MASTER_TABLE' + tables['M2C'] = 'HLT_TM_TO_TC' + tables['TC'] = 'HLT_TRIGGER_CHAIN' + + + condition = [ "SM.SMT_ID = :smk", + 'SM.SMT_HLT_MASTER_TABLE_ID = HM.HMT_ID', + 'HM.HMT_TRIGGER_MENU_ID = M2C.HTM2TC_TRIGGER_MENU_ID', + 'M2C.HTM2TC_TRIGGER_CHAIN_ID = TC.HTC_ID' ] + + bindvars = { "smk": smk } + + res = executeQuery(__cursor, output, condition, __schema, tables, bindvars) + + l2chains = [] + efchains = [] + + for r in res: + if r[3]=='L2': + l2chains += [TriggerChain(r[0],r[1],r[2], level=2)] + elif r[3]=='EF': + efchains += [TriggerChain(r[0],r[1],r[2], level=3)] + + return l2chains, efchains + +def getL1Menu(smk): + global __cursor, __schema + if not __cursor: + __cursor,__schema = getTriggerDBCursor("TRIGGERDB") + + output = ['TI.L1TI_NAME', 'TI.L1TI_CTP_ID' ] + + tables = {} + tables['SM'] = 'SUPER_MASTER_TABLE' + tables['M'] = 'L1_MASTER_TABLE' + tables['M2I'] = 'L1_TM_TO_TI' + tables['TI'] = 'L1_TRIGGER_ITEM' + + + condition = [ "SM.SMT_ID = :smk", + 'SM.SMT_L1_MASTER_TABLE_ID = M.L1MT_ID', + 'M.L1MT_TRIGGER_MENU_ID = M2I.L1TM2TI_TRIGGER_MENU_ID', + 'M2I.L1TM2TI_TRIGGER_ITEM_ID = TI.L1TI_ID' ] + + bindvars = { "smk": smk } + + res = executeQuery(__cursor, output, condition, __schema, tables, bindvars) + + items = 256*[None] + + for r in res: + items[r[1]] = TriggerChain(r[0],r[1]) + return items + +def getL1Prescales(l1prescalekey): + global __cursor, __schema + if not __cursor: + __cursor,__schema = getTriggerDBCursor("TRIGGERDB") + + tables = { 'L' : 'L1_PRESCALE_SET' } + + output = ['L.L1PS_VAL%i' % i for i in xrange(1,257)] + + condition = [ "L.L1PS_ID = :psk" ] + + bindvars = { "psk": l1prescalekey } + + res = executeQuery(__cursor, output, condition, __schema, tables, bindvars) + + return res[0][0:256] + + +def getHLTPrescales(hltprescalekey): + global __cursor, __schema + if not __cursor: + __cursor,__schema = getTriggerDBCursor("TRIGGERDB") + + tables = { 'H' : 'HLT_PRESCALE_SET', + 'P' : 'HLT_PRESCALE' + } + + output = [ 'P.HPR_L2_OR_EF', 'P.HPR_CHAIN_COUNTER', 'P.HPR_PRESCALE', 'P.HPR_PASS_THROUGH_RATE' ] + + condition = [ "P.HPR_PRESCALE_SET_ID = :psk", "P.HPR_L2_OR_EF not like 'express'" ] + + bindvars = { "psk": hltprescalekey } + + res = executeQuery(__cursor, output, condition, __schema, tables, bindvars) + + l2ps = {} + efps = {} + for r in res: + if r[0]=='L2': l2ps[r[1]] = ( float(r[2]), float(r[3]) ) + elif r[0]=='EF': efps[r[1]] = ( float(r[2]), float(r[3]) ) + + return l2ps, efps + + +def getRandom(smk): + global __cursor, __schema + if not __cursor: + __cursor,__schema = getTriggerDBCursor("TRIGGERDB") + + if smk=='n.a.': + return ('n.a.','n.a.') + + tables = { 'S' : 'SUPER_MASTER_TABLE', + 'M' : 'L1_MASTER_TABLE', + 'R' : 'L1_RANDOM' + } + + output = [ 'R.L1R_RATE1', 'R.L1R_RATE2' ] + + condition = [ "S.SMT_ID=:smk", "S.SMT_L1_MASTER_TABLE_ID=M.L1MT_ID", "M.L1MT_RANDOM_ID=R.L1R_ID" ] + + bindvars = { "smk": smk } + + res = executeQuery(__cursor, output, condition, __schema, tables, bindvars) + + return res[0] + + + +if __name__=="__main__": + #print getL1PskNames([2517, 2518, 2284, 2536]) + + #print getHLTPskNames([2517, 2530, 2537, 2491]) + + #print getSmkNames([931, 932, 933]) + + #print getL1Menu(931) + + #print getHLTMenu(931) + + #print getL1Prescales(2517) + + #print getHLTPrescales(2530) + + print getRandom(1038) diff --git a/Database/CoolRunQuery/python/utils/AtlRunQueryUtils.py b/Database/CoolRunQuery/python/utils/AtlRunQueryUtils.py new file mode 100644 index 00000000000..8160a3fa187 --- /dev/null +++ b/Database/CoolRunQuery/python/utils/AtlRunQueryUtils.py @@ -0,0 +1,633 @@ +#!/bin/env + +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration +# +# ---------------------------------------------------------------- +# Script : AtlRunQueryUtils.py +# Project: AtlRunQuery +# Purpose: Utility functions for AtlRunQuery +# Authors: Andreas Hoecker (CERN), Joerg Stelzer (DESY) +# Created: Jan 20, 2009 +# ---------------------------------------------------------------- +# + +from __future__ import with_statement +from .AtlRunQueryTimer import timer + +from collections import namedtuple, defaultdict +import sys, os, time, re, calendar +from math import exp,sqrt,pi +from PyCool import cool +from copy import copy, deepcopy +import cx_Oracle +import struct +import urllib + +from httplib import HTTP +from urlparse import urlparse + +import DQDefects + +def checkURL(url): + p = urlparse(url) + h = HTTP(p[1]) + h.putrequest('HEAD', p[2]) + h.endheaders() + if h.getreply()[0] == 200: return 1 + else: return 0 + +class RunPeriods(): + def findPeriod( self, runno ): + from CoolRunQuery.AtlRunQueryCOMA import ARQ_COMA + s = ",".join( ARQ_COMA.get_periods_for_run(runno) ) + return s + +class Enumerate(object): + def __init__(self, names): + for number, name in enumerate(names.split()): + setattr(self, name, number) + +def runsOnServer(): + hostname = os.getenv('HOSTNAME') + return re.match('voatlas(197|218|262).cern.ch',hostname) + +def importroot(batch=True): + import sys + cmdline_args = sys.argv + sys.argv[1:]=[] + if batch: + sys.argv += ['-b'] + import ROOT + ROOT.gROOT.SetBatch(batch) + if batch: + del sys.argv[1] + # import PyCintex + # PyCintex.Cintex.Enable() + ROOT.gErrorIgnoreLevel = 2000 # to avoid printout when creating images + sys.argv=cmdline_args + return ROOT + + +def prettyNumber( n, width=-1, delim=',',decimal='.' ): + """Converts a float to a string with appropriately placed commas""" + if width >= 0: s = "%.*f" % (width, n) + else: s = str(n) + dec = s.find(decimal) + if dec == -1: dec = len(s) + threes = int((dec-1)/3) + for i in xrange(threes): + loc = dec-3*(i+1) + s = s[:loc] + delim + s[loc:] + return s + +def durationInSeconds( duration_string ): + lst = duration_string.split() + sec = 0 + for l in lst: + if 's' in l: sec += int(l.replace('s','')) + elif 'm' in l: sec += int(l.replace('m',''))*60 + elif 'h' in l: sec += int(l.replace('h',''))*3600 + elif 'd' in l: sec += int(l.replace('d',''))*3600*24 + elif 'w' in l: sec += int(l.replace('w',''))*3600*24*7 + elif 'y' in l: sec += int(l.replace('y',''))*3600*24*365 + else: + print 'Big troubles... in function "AtlRunQueryUtils::durationInSeconds": cannot decode string "%s"' % l + sys.exit(1) + return sec + +class DBConnectionController: + def __init__(self): + self.openConn = {} + self.pw = {} + + def GetDBConn(self, schema, db): + """for example schema=COOLONL_TRIGGER', db='COMP200""" + if (schema,db) in self.openConn: + return self.openConn[(schema,db)] + try: + if schema=="DEFECTS": + #from AthenaCommon.Logging import logging # this is needed because of some problem in DQUtils logger + # the logger there, if called first makes the athena logger crash + defdb = DQDefects.DefectsDB("COOLOFL_GLOBAL/COMP200",tag=db) + defdb.closeDatabase = lambda: None + self.openConn[(schema,db)] = defdb + else: + readoracle=False # Richard: Tools which expect to read from the real + # data conditions database have to be + # setup in such a way as to ignore SQLite + # replicas have to use 'indirectOpen' with + # the oracle=True argument + logging=False + from CoolConvUtilities.AtlCoolLib import indirectOpen + self.openConn[(schema,db)] = indirectOpen("%s/%s"%(schema,db),True,readoracle, logging) + except Exception, e: + print e + sys.exit(-1) + return self.openConn[(schema,db)] + + def get_auth(self,key): + if key not in self.pw: + from os import environ as env + #lookup = XMLReader(env['CORAL_DBLOOKUP_PATH']+"/dblookup.xml") + #for s in lookup.servicelist.logicalservices: + # print "Service",s['name'] + # for p in s.services: + # print " name",p['name'] + auth = XMLReader(env['CORAL_AUTH_PATH']+"/authentication.xml") + for c in auth.connectionlist.connections: + if key!=c['name']: continue + self.pw[key] = dict([(p['name'],p['value']) for p in c.parameters]) + break + if key not in self.pw: + print "Can not authenticate DB",key + sys.exit(0) + return self.pw[key] + + + def GetAtlasRunDBConnection(self): + if not 'run' in self.openConn: + auth = self.get_auth('oracle://ATLAS_COOLPROD/ATLAS_COOLOFL_TRIGGER') + self.openConn['run'] = cx_Oracle.connect("%s/%s@ATLAS_COOLPROD" % (auth['user'],auth['password'])) + return self.openConn['run'] + + def GetSFODBConnection(self): + if not 'sfo' in self.openConn: + #auth = self.get_auth('oracle://ATLAS_CONFIG/ATLAS_SFO_T0') + #self.openConn['sfo'] = cx_Oracle.connect("%s/%s@ATLAS_CONFIG" % (auth['user'],auth['password'])) + self.openConn['sfo'] = cx_Oracle.connect("ATLAS_SFO_T0_R/readmesfotz2008@ATLAS_CONFIG") + return self.openConn['sfo'] + + def GetTier0DBConnection(self): + if not 'tier0' in self.openConn: + #auth = self.get_auth('oracle://ATLAS_T0/ATLAS_T0') + #self.openConn['tier0'] = cx_Oracle.connect("%s/%s@ATLAS_T0" % (auth['user'],auth['password'])) + self.openConn['tier0'] = cx_Oracle.connect("ATLAS_T0_R1/titft0ra@ATLAS_T0") + return self.openConn['tier0'] + + def GetPVSSDBConnection(self): + if not 'pvss' in self.openConn: + #auth = self.get_auth('oracle://ATLAS_PVSSPROD/ATLAS_PVSS_READER') + #self.openConn['pvss'] = cx_Oracle.connect("%s/%s@ATLAS_PVSSPROD" % (auth['user'],auth['password'])) + self.openConn['pvss'] = cx_Oracle.connect("ATLAS_PVSS_READER/PVSSRED4PRO@ATLAS_PVSSPROD") + return self.openConn['pvss'] + + def CloseAll(self): + for (dbname,dbconn) in self.openConn.items(): + if isinstance(dbconn,cx_Oracle.Connection): + dbconn.close() + else: + dbconn.closeDatabase() + + +coolDbConn = DBConnectionController() + +def addKommaToNumber(no): + gr = [str(no)] + if not gr[0].isdigit(): return gr[0] + while gr[0]: + gr[0:1]=[gr[0][:-3],gr[0][-3:]] + return ','.join(gr[1:]) + +def filesize(no): + if no == None: return "n.a." + if no>0x4000000000000: return "%1.1f PB" % (no*1.0/0x4000000000000) + if no>0x10000000000: return "%1.1f TB" % (no*1.0/0x10000000000) + if no>0x40000000: return "%1.1f GB" % (no*1.0/0x40000000) + if no>0x100000: return "%1.1f MB" % (no*1.0/0x100000) + if no>0x400: return "%1.1f kB" % (no*1.0/0x400) + return "%i B" % no + + +# a helper function to decode range lists and merge the intervals +# par: listOfRanges: [(b1,e1),(b2,e2),....,(bn,en)] -> [(b1,e2),....,(bn,en)] if b2-1<=e1 +def MergeRanges(listOfRanges): + listOfRanges.sort() + newRR=[] + for rr in listOfRanges: + if len(newRR)==0 or rr[0]-1>newRR[-1][1]: + newRR.append(copy(rr)) + else: + newRR[-1] = [newRR[-1][0], max(rr[1],newRR[-1][1]) ] + return newRR + +# a helper function to decode range lists and merge the intervals (like above) +# par: listOfRanges: [(b1,e1],(b2,e2],....,(bn,en]] -> [(b1,e2],....,(bn,en]] if b2<=e1 +def MergeLBRanges(listOfRanges): + listOfRanges.sort() + newRR=[] + for rr in listOfRanges: + if len(newRR)==0 or rr[0]>newRR[-1][1]: + newRR.append(copy(rr)) + else: + newRR[-1] = (newRR[-1][0], max(rr[1],newRR[-1][1]) ) + return newRR + +class Matrix(object): + + def __init__(self, cols, rows): + self.cols = cols + self.rows = rows + # initialize matrix and fill with zeroes + self.matrix = [] + for i in range(rows): + ea_row = [] + for j in range(cols): + ea_row.append(0) + self.matrix.append(ea_row) + + def setitem(self, col, row, v): + self.matrix[col-1][row-1] = v + + def getitem(self, col, row): + return self.matrix[col-1][row-1] + + def __repr__(self): + outStr = "" + for i in range(self.rows): + outStr += 'Row %s = %s\n' % (i+1, self.matrix[i]) + return outStr + + def __iter__(self): + for row in range(self.rows): + for col in range(self.cols): + yield (self.matrix, row, col) + +def stringToIntOrTime(s): + if re.match("^\d+$",s): return int(s) + # convert the string into seconds since epoch + + # string is meant to be UTC time (hence we need calendar.timegm + # and not time.mktime to convert the tuple into seconds) + t = int(1E9*calendar.timegm(time.strptime(s,"%d.%m.%Y"))) + #print "turning",s,"into time",t + #print "cross-check:",time.strftime("%d.%m.%Y %H:%M:%S",time.gmtime(t/1E9)) + return t + +def timeStringToSecondsUTC(t): + """ convert string into seconds since epoch + + string is meant to be UTC time (hence we need calendar.timegm + and not time.mktime to convert the tuple into seconds) + + format can be '1.5.2010_14:23:10', '1.5.2010 14:23:10', or '1.5.2010' + """ + try: + t = time.strptime(t,"%d.%m.%Y") + except ValueError: + try: + t = time.strptime(t,"%d.%m.%Y %H:%M:%S") + except: + t = time.strptime(t,"%d.%m.%Y_%H:%M:%S") + + return int(calendar.timegm(t)) + +def timeStringToSecondsLocalTime(t): + """ convert string into seconds since epoch + + string is meant to be in local time (hence we use time.mktime + and not calender.timegm to convert the tuple into seconds) + + format can be '1.5.2010_14:23:10', '1.5.2010 14:23:10', or '1.5.2010' + """ + try: + t = time.strptime(t,"%d.%m.%Y") + except ValueError: + try: + t = time.strptime(t,"%d.%m.%Y %H:%M:%S") + except: + t = time.strptime(t,"%d.%m.%Y_%H:%M:%S") + + return int(time.mktime(t)) + +def secondsToTimeStringUTC(s): + # convert seconds since epoch into utc + return time.strftime("%d.%m.%Y %H:%M:%S",time.gmtime(s)) + + +def secondsToTimeStringLocaltime(s): + # convert seconds since epoch into localtime + return time.strftime("%d.%m.%Y %H:%M:%S",time.localtime(s)) + + + +def GetRanges(rangestr, intRepFnc=stringToIntOrTime, maxval=1<<30): + if type(rangestr)==list: + ranges = rangestr + else: + if rangestr[:4]=='STR:': + return [ [rangestr[4:], rangestr[4:]] ] + ranges = rangestr.split(',') + listOfRanges = [] + for r in ranges: + if r == '-': + listOfRanges += [[0,maxval]] + elif not ('-' in r or '+' in r): + # single value + x = intRepFnc(r) + listOfRanges += [[x,x]] + elif r[-1] == '+': + listOfRanges += [[intRepFnc(r[:-1]),maxval]] + elif r[-1] == '-': + listOfRanges += [[0,intRepFnc(r[:-1])]] + else: + startend = r.split('-') + if len(startend)!=2: + raise RuntimeError, "Range format error '%s'" % r + listOfRanges += [[intRepFnc(x) for x in startend]] + return MergeRanges(listOfRanges) + + + + +def full_time_string(s,startofday): + try: + time.strptime(s,"%d.%m.") + year=str(time.gmtime().tm_year) + return s+year+"_00:00:00" if startofday else s+year+"_23:59:59" + except: + try: + time.strptime(s,"%d.%m.%Y") + return s+"_00:00:00" if startofday else s+"_23:59:59" + except: + return s + +def GetTimeRanges(timeranges, intRepFnc=timeStringToSecondsUTC, maxval=1<<30): + if type(timeranges)==list: + timeranges = ','.join(timeranges) + + timeranges = timeranges.split(',') + listOfRanges = [] + listOfRangesHR = [] + + for r in timeranges: + start = 0 + end = 'inf' + if r == '-': + listOfRangesHR += [['0','inf']] + elif not ('-' in r or '+' in r): + # single value + start = full_time_string( r, startofday=True ) + end = full_time_string( r, startofday=False ) + elif r[-1] == '+': + start = full_time_string( r[:-1], startofday=True ) + elif r[-1] == '-': + end = full_time_string( r[:-1], startofday=False ) + else: + try: + start,end = r.split('-') + except: + raise RuntimeError, "Time format '%s' wrong, should be 'from-until'" % r + start = full_time_string( start, startofday=True ) + end = full_time_string( end, startofday=False ) + + listOfRangesHR += [[start,end]] + start = 0 if start==0 else intRepFnc(start) + end = maxval if end=='inf' else intRepFnc(end) + listOfRanges += [[start,end]] + + return MergeRanges(listOfRanges),listOfRangesHR + + +def SmartRangeCalulator(runlist,singleRuns=False): + if len(runlist) == 0: + return [] + if type(runlist[0]) == type([]): + return runlist + if singleRuns: + rr = [[r.runNr,r.runNr] for r in runlist] + else: + rr = [[runlist[0].runNr,runlist[-1].runNr]] + return rr + + +def UnpackBCID(blob, nb1, nb2, nbc): + size = nb1+nb2+nbc + a = struct.unpack( size*'H', blob[:2*size]) + return a[0:nb1], a[nb1:nb1+nb2], a[nb1+nb2:] # beam1, beam2, coll + + +def Poisson( n, mu ): + # only valid for small mu and integer n + if n < 0 : return 0 + else: + p = exp(-mu) + for i in xrange(n): + p *= mu + p /= float(i+1) + return p + +def ComputePileup( lumi_mb, sigma, nbunch, fOrbit ): + # sanity + if nbunch <= 0: return 0 + + # compute first probability of interaction + nint = lumi_mb*sigma/fOrbit/nbunch + if nint > 100: return nint + + # small 'nint', compute poisson probabilities + p = [] + for n in xrange(40): + p.append(Poisson(n,nint)) + if n > 20 and p[-1] < 1e40: break + return nint, p + +def Pileup( args ): + # orbit revolution frequency + fOrbit = 11245.511 # [Hz] given by: 40.0790e6 Hz (bunch frequency) / 3564 (nbunches) + + if len(args) < 3: + return '<font color="red">ERROR: need at least 3 arguments for pileup calculation: inst.-lumi cross-section-in-mb nbunch [nevents]</font>' + try: + fstarg = float(args[0]) + if fstarg < 1e20: + # input expects first two arguments to be number of protons and transverse beam size + nprotons = fstarg + sigtrans = float(args[1]) # given in microns + sigtrans *= 1e-4 # transform to cm + + sigma = float(args[2]) + nbunch = float(args[3]) + lumi = nprotons**2 * fOrbit * nbunch / (4.0 * pi * sigtrans**2) + nevents = 1 + if len(args) == 5: nevents = float(args[4]) + else: + # input expects first argument to be luminosity in cm-2s-1 + lumi = float(args[0]) + sigma = float(args[1]) + nbunch = float(args[2]) + nevents = 1 + if len(args) == 4: nevents = float(args[3]) + + # compute pileup + lumi_mb = lumi/1.e27 # luminosity in mb-1s-1 + pint, plist = ComputePileup( lumi_mb, sigma, nbunch, fOrbit ) + + # create output string + s = ' ' + s += '<table class="datasettable" style="font-size: 140%">' + s += '<tr><td>Instantaneous luminosity :</td><td> %g cm−2s−1 (= %g mb−1s−1)</td></tr>' % (lumi, lumi_mb) + s += '<tr><td>Inelastic cross section :</td><td> %g mb</td></tr>' % sigma + s += '<tr><td>Number of colliding bunches :</td><td> %g</td></tr>' % nbunch + s += '<tr><td colspan="2"><hr style="width:100%; #999999; background-color: #999999; height:1px; margin-left:0px; border:0"></td></tr>' + s += '<tr><td>Inelastic interaction rate:</td><td>%g Hz</td></tr>' % (lumi_mb*sigma) + s += '<tr><td>Average number of interactions per crossing: </td><td> %g</td></tr>' % pint + s += '</table>' + s += '<hr style="width:100%; #999999; background-color: #999999; height:0px; border:0">\n<p>\n' + if not plist: + p = plist[0] + s += 'Very large pileup probability (assume Gaussian distribution): %g +- %g' % (p, sqrt(p)) + else: + s += '<table class="pileuptable">' + s += '<tr><th>Num. of interactions per filled bunch crossing</th><th>Probability per filled bunch crossing</th><th>Probability per triggered minimum bias event*</th>' + if nevents>1: + s += '<th>Expected number of events in sample*</th>' + s += '</tr>' + + pref = 1.0-plist[0] # probability for a zero bias trigger to have triggered an event + psum = 0 + for i,p in enumerate(plist): + if i>=1: + s += '<tr><td>>= %i</td><td>%g</td><td>%g</td>' % (i,1.0-psum,(1.0-psum)/pref) + if nevents > 1: + nevexp = (1.0-psum)/pref*nevents + s += '<td> %g</td>' % (nevexp) + s += '</tr>' + if p < 1e-15: break + psum += p + s += '</table><p></p>' + s += '<font size=-2>*assuming 100% trigger efficiency for inelastic events</font><br>' + + return s + + except ValueError: + return '<font color="red">ERROR: only numerical arguments allowed</font>' + + +def get_run_range(start,end=None): + runs = get_runs(start,end) + if len(runs) == 0: return None + if len(runs) == 1: return (runlist[0],runlist[0]) + return (runs[0],runs[-1]) + +def get_runs_last_dt(last): + lt = last.replace('last','').strip() + nsec = durationInSeconds( lt ) + start = time.gmtime( time.mktime(time.gmtime()) - nsec ) + return get_runs(time.strftime("%d.%m.%Y_%H:%M:%S",start)) + +def get_runs(start,end=None): + """start and end are given in the format '1.5.2010_14:23:10', '1.5.2010 14:23:10', or '1.5.2010'""" + + co = coolDbConn.GetAtlasRunDBConnection() + cu = co.cursor() + + #available: + # NAME,RUNNUMBER,STARTAT,DURATION,CREATEDBY,HOST,PARTITIONNAME,CONFIGSCHEMA,CONFIGDATA,COMMENTS + records = 'RUNNUMBER' + + t = time.gmtime( timeStringToSecondsUTC(start) ) + starttime = time.strftime("%Y%m%dT%H%M%S",t) + + if not end: + q = "SELECT %s FROM ATLAS_RUN_NUMBER.RUNNUMBER WHERE STARTAT>'%s' ORDER BY RUNNUMBER desc" % (records,starttime) + else: + t = time.gmtime( timeStringToSecondsUTC(end) ) + endtime = time.strftime("%Y%m%dT%H%M%S",t) + q = "SELECT %s FROM ATLAS_RUN_NUMBER.RUNNUMBER WHERE STARTAT>'%s' and STARTAT<'%s' ORDER BY RUNNUMBER" % (records,starttime,endtime) + + cu.arraysize=100 + cu.execute(q) + + res = cu.fetchall() + return [r[0] for r in res] + + +def get_run_range2(start,end=None): + """start and end are given in the format '1.5.2010_14:23:10', '1.5.2010 14:23:10', or '1.5.2010'""" + + co = coolDbConn.GetAtlasRunDBConnection() + cu = co.cursor() + + #available: + # NAME,RUNNUMBER,STARTAT,DURATION,CREATEDBY,HOST,PARTITIONNAME,CONFIGSCHEMA,CONFIGDATA,COMMENTS + + t = time.gmtime( timeStringToSecondsUTC(start) ) + sstart = time.strftime("%Y%m%dT%H%M%S",t) + + # last run that started before the begin of the range + subq = "SELECT MAX(RUNNUMBER) FROM ATLAS_RUN_NUMBER.RUNNUMBER WHERE STARTAT<'%s'" % sstart + q = "select RUNNUMBER,STARTAT,DURATION from ATLAS_RUN_NUMBER.RUNNUMBER where RUNNUMBER=(%s)" % subq + cu.execute(q) + try: + run1,startat,duration = cu.fetchone() + # note that duration is not exact, but always a bit larger than the length of the run + + endat = calendar.timegm( time.strptime(startat,"%Y%m%dT%H%M%S") ) + duration + if end < timeStringToSecondsUTC(start): + q = "SELECT MIN(RUNNUMBER) FROM ATLAS_RUN_NUMBER.RUNNUMBER WHERE STARTAT>'%s'" % sstart + cu.execute(q) + try: + run1 = cu.fetchone()[0] + except: + run1 = None + except: + run1 = None + + run2 = None + if end != None: + # last run that started before the end of the range + t = time.gmtime( timeStringToSecondsUTC(end) ) + endtime = time.strftime("%Y%m%dT%H%M%S",t) + q = "SELECT MAX(RUNNUMBER) FROM ATLAS_RUN_NUMBER.RUNNUMBER WHERE STARTAT<'%s'" % endtime + cu.execute(q) + run2 = cu.fetchone()[0] + + return (run1,run2) + + + +class XMLReader(object): + class XMLElement: + def __init__(self,element): + self.element = element + self.tag = element.tag + self.attributes = dict(element.items()) + self.children = element.getchildren() + self.readchildren() + + def items(self): + return self.attributes.items() + def __str__(self): + return "<%s %s>" % (self.tag, " ".join(['%s="%s"' % x for x in self.items()])) + def __repr__(self): + return self.tag + def __getitem__(self,k): + if not k in self.attributes: + raise KeyError, "'%s'. XML element '%s' has attributes %s" % (k,self.tag, self.attributes.keys()) + return self.attributes[k] + + def readchildren(self): + self.childtags = [] + self._childtagdict = {} + for c in self.children: + self._childtagdict.setdefault(c.tag,[]).append(XMLReader.XMLElement(c)) + if not c.tag in self.childtags: self.childtags += [c.tag] + for t in self.childtags: + self.__dict__['%ss'%t] = self._childtagdict[t] + if len(self._childtagdict[t])==1: + self.__dict__['%s'%t] = self._childtagdict[t][0] + + def __getattr__(self,name): + raise AttributeError, "'%s'. XML element '%s' has tags %s" % (name,self.tag, ["%ss" % t for t in self.childtags]) + + + def __init__(self,filename): + import xml.etree.cElementTree as ET + self.doc = ET.parse(filename) + root = XMLReader.XMLElement(self.doc.getroot()) + self.__filename = filename + self.__root = root + self.__dict__[root.tag] = root + + def __getattr__(self,name): + raise AttributeError, "'%s'. XML document '%s' has root tag '%s'" % (name,self.__filename, self.__root.tag) diff --git a/Database/CoolRunQuery/python/utils/__init__.py b/Database/CoolRunQuery/python/utils/__init__.py new file mode 100644 index 00000000000..74583d364ec --- /dev/null +++ b/Database/CoolRunQuery/python/utils/__init__.py @@ -0,0 +1,2 @@ +# Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + diff --git a/Database/CoolRunQuery/share/AtlRunQuery.py b/Database/CoolRunQuery/share/AtlRunQuery.py new file mode 100755 index 00000000000..e8aa05f57a8 --- /dev/null +++ b/Database/CoolRunQuery/share/AtlRunQuery.py @@ -0,0 +1,83 @@ +#!/bin/env python +# +# ---------------------------------------------------------------- +# Script : AtlRunQuery.py +# Project: AtlRunQuery +# Purpose: Main +# Authors: Andreas Hoecker (CERN), Joerg Stelzer (DESY) +# Created: Nov 13, 2008 +# ---------------------------------------------------------------- +# + +import sys, getopt, os + +def main(): + arguments = sys.argv + + if len(arguments) <= 1: + print 'No query argument given' + return 1 + + fileindex = None + + if arguments[1] == 'fileindex': + installpath = '/'.join(os.path.dirname(__file__).split('/')[:-1]) + fileindex = arguments[2] + datapath = 'data/arq_%s/arq_%s' % (fileindex[:6],fileindex) + queryfile = '%s/%s/query.txt' % (installpath,datapath) + fh = open(queryfile,"r") + origQuery = fh.readline().strip() + fh.close() + arguments[1:] = origQuery.split() + else: + datapath='data' + origQuery = ' '.join(arguments[1:]) + + ignoreParser = origQuery.startswith('--') + + from CoolRunQuery.AtlRunQueryOptions import AtlRunQueryOptions + if ignoreParser: + + (options, args) = AtlRunQueryOptions().parse() + atlqueryarg = origQuery + + else: + + from CoolRunQuery.AtlRunQueryParser import ArgumentParser + ap = ArgumentParser() + + # special treatment of some extra tools: pileup calc, det mask decoding, help(not implemented) + firstarg = arguments[1].lower() + if firstarg in ['pileup','detmask','help']: + tabletemplate = '<table class="othertable" style="width:60%%"><tr><th>Query result:</th></tr><tr><td>\n%s</td></tr></table><p></p><br><p></p>' + if firstarg == 'pileup': + from CoolRunQuery.AtlRunQueryUtils import Pileup + body = tabletemplate % (Pileup(arguments[2:])) + elif firstarg == 'detmask': + from CoolRunQuery.AtlRunQueryLookup import DecodeDetectorMask + body = tabletemplate % (DecodeDetectorMask( int(arguments[2] ))) + else: + body = '' + + # return a default page + from CoolRunQuery.AtlRunQueryPageMaker import PageMaker + PageMaker.makeDefaultPage(datapath, body, origQuery) + return 0 + + atlqueryarg = arguments[0].rsplit('/',1)[-1] + " " + ap.ParseArgument( ' '.join(arguments[1:]) ) + + if '--verbose' in atlqueryarg: + print atlqueryarg + + (options, args) = AtlRunQueryOptions().parse(atlqueryarg) + + + from CoolRunQuery.AtlRunQueryLib import AtlRunQuery + AtlRunQuery(options, html="AUTO", origQuery=origQuery, datapath=datapath, parsedstring=atlqueryarg) # html can be "YES", "NO", "AUTO" + return 0 + +if __name__ == '__main__': + #import cProfile + #cProfile.run('main()','prof') + import sys + sys.exit(main()) diff --git a/Database/CoolRunQuery/share/AtlRunQueryGraphs.py b/Database/CoolRunQuery/share/AtlRunQueryGraphs.py new file mode 100755 index 00000000000..9bc234a1398 --- /dev/null +++ b/Database/CoolRunQuery/share/AtlRunQueryGraphs.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python + +import sys,os +from CoolRunQuery.AtlRunQueryLookup import InitDetectorMaskDecoder, DQChannels +from ROOT import TFile, TGraph, TH1I + +def execute( fname ): + file = open( fname, 'r' ) + + # first count number of lines in file and find run numbers + ic = -1 + minRun = 1e20 + maxRun = -1 + for line in file: + if '--------------------' in line: + if ic<0: + ic = 0 + continue + else: break + if ic >= 0: + # get run number + r, sep, line = line.partition(':') + runno = int(r.strip('run').strip()) + if runno > maxRun: maxRun = runno + if runno < minRun: minRun = runno + ic += 1 + + print 'Found: %i runs in file; run range: [%i, %i]' % (ic,minRun, maxRun) + maxRun += 1 + minRun -= 1 + + # --- read detectors ---- + + print ' ' + print 'Reading detectors and DQ flags' + (dName, NotInAll) = InitDetectorMaskDecoder() + dqchans = DQChannels() + + # create graphs and histograms + rootfile = TFile.Open( "queryresults.root", "RECREATE" ) + detgraphs = {} + dethists = {} + for det in dName: + detc = det.strip().replace(' ','_') + if 'unknown' in detc: continue + + detgraphs[det] = TGraph( ic ) + detgraphs[det].SetName( 'Det_%s_vs_run_graph' % detc ) + dethists[det] = TH1I( 'Det_%s_vs_run_hist' % detc, 'Det_%s_vs_run_hist' % detc, maxRun-minRun+1, minRun, maxRun ) + + dqhists = {} + for key, dq in dqchans.items(): + dqhists[dq] = TH1I( 'DQ_%s_vs_run_hist' % dq, 'DQ_%s_vs_run_hist' % dq, maxRun-minRun+1, minRun, maxRun ) + + # read lines + ip = -1 + file.seek(0) + for line in file: + # search for start of output block + if '--------------------' in line: + for line in file: + if '--------------------' in line: break + # get run number + r, sep, line = line.partition(':') + ip += 1 + runno = int(r.strip('run').strip()) + + # check detector + for det in dName: + if 'unknown' in det: continue + if det in line: v = 2 + else: v = 1 + detgraphs[det].SetPoint( ip, runno, v ) + dethists[det].SetBinContent( runno - minRun + 1, v ) + + # check DQ flag + for key, dq in dqchans.items(): + if not dq in line: v = -1 + else: + word = line[line.find( dq ):line.find( dq )+len(dq)+2] + if '=' in word: + w, sep, status = word.partition('=') + status = status.strip().lower() + if status == 'u': v = 0 + elif status == 'g': v = 3 + elif status == 'y': v = 2 + elif status == 'r': v = 1 + else: + print 'ERROR: unknown DQ status: "%s" --> abort' % status + sys.exit(1) + + dqhists[dq].SetBinContent( runno - minRun + 1, v ) + else: + print 'ERROR: format error on DQ parsing: "%s" --> abort' % word + sys.exit(1) + + + print 'End of file reading. Found: %i runs' % (ip+1) + + # write all graphs, and finish + for key, g in detgraphs.items(): g.Write() + for key, h in dethists.items() : h.Write() + for key, h in dqhists.items() : h.Write() + + print 'Wrote root file: %s' % rootfile.GetName() + rootfile.Close() + + +# command line driver for convenience +if __name__=='__main__': + + if len(sys.argv) <= 1: + print 'ERROR: Need to give input filename as argument --> abort' + sys.exit(0) + + execute( sys.argv[1] ) diff --git a/Database/CoolRunQuery/share/CoolRunQueryWrapper.sh b/Database/CoolRunQuery/share/CoolRunQueryWrapper.sh new file mode 100755 index 00000000000..035b70230f9 --- /dev/null +++ b/Database/CoolRunQuery/share/CoolRunQueryWrapper.sh @@ -0,0 +1,47 @@ +export PATH=\ +/cvmfs/atlas.cern.ch/repo/sw/software/17.0.5/sw/lcg/external/Python/2.6.5/i686-slc5-gcc43-opt/bin:\ +$PATH + +export ROOTSYS=/cvmfs/atlas.cern.ch/repo/sw/software/17.0.5/sw/lcg/app/releases/ROOT/5.28.00g/i686-slc5-gcc43-opt/root + +export PYTHONPATH=`pwd`:\ +/cvmfs/atlas.cern.ch/repo/sw/software/17.0.5/AtlasProduction/17.0.5.6/InstallArea/python:\ +/cvmfs/atlas.cern.ch/repo/sw/software/17.0.5/AtlasConditions/17.0.5/InstallArea/python:\ +/cvmfs/atlas.cern.ch/repo/sw/software/17.0.5/AtlasCore/17.0.5/InstallArea/i686-slc5-gcc43-opt/lib/python2.6:\ +/cvmfs/atlas.cern.ch/repo/sw/software/17.0.5/AtlasCore/17.0.5/InstallArea/python:\ +/cvmfs/atlas.cern.ch/repo/sw/software/17.0.5/AtlasTrigger/17.0.5/InstallArea/python:\ +/cvmfs/atlas.cern.ch/repo/sw/software/17.0.5/external/ZSI/2.1-a1/lib/python:\ +/cvmfs/atlas.cern.ch/repo/sw/software/17.0.5/sw/lcg/app/releases/COOL/COOL_2_8_10b/i686-slc5-gcc43-opt/python:\ +$ROOTSYS/lib:\ +/cvmfs/atlas.cern.ch/repo/sw/software/17.0.5/sw/lcg/external/pygraphics/1.1p1_python2.6/i686-slc5-gcc43-opt/lib/python2.6/site-packages:\ +/cvmfs/atlas.cern.ch/repo/sw/software/17.0.5/sw/lcg/external/pytools/1.4_python2.6/i686-slc5-gcc43-opt/lib/python2.6/site-packages + +export LD_LIBRARY_PATH=\ +/cvmfs/atlas.cern.ch/repo/sw/software/17.0.5/AtlasConditions/17.0.5/InstallArea/i686-slc5-gcc43-opt/lib:\ +/cvmfs/atlas.cern.ch/repo/sw/software/17.0.5/DetCommon/17.0.5/InstallArea/i686-slc5-gcc43-opt/lib:\ +/cvmfs/atlas.cern.ch/repo/sw/software/17.0.5/LCGCMT/LCGCMT_60d/InstallArea/i686-slc5-gcc43-opt/lib:\ +/cvmfs/atlas.cern.ch/repo/sw/software/17.0.5/sw/lcg/external/Python/2.6.5/i686-slc5-gcc43-opt/lib:\ +/afs/cern.ch/sw/lcg/external/gcc/4.3.2/x86_64-slc5/lib:\ +/usr/lib + +# for the dbreplica.config file +export DATAPATH=\ +/cvmfs/atlas.cern.ch/repo/sw/software/17.0.5/AtlasCore/17.0.5/InstallArea/share + +export CORAL_DBLOOKUP_PATH=/data/atrqadm/authentication +export CORAL_AUTH_PATH=/data/atrqadm/authentication + +export TNS_ADMIN=/cvmfs/atlas.cern.ch/repo/sw/software/17.0.5/DBRelease/current/oracle-admin + +export HOSTNAME=$HOSTNAME + +export NLS_LANG=AMERICAN_AMERICA.WE8ISO8859P1 + +export COOL_DISABLE_CORALCONNECTIONPOOLCLEANUP=YES + +unset HOME + +printenv + +`pwd`/share/AtlRunQuery.py $* + diff --git a/Database/CoolRunQuery/share/CoolRunQueryWrapper47.sh b/Database/CoolRunQuery/share/CoolRunQueryWrapper47.sh new file mode 100755 index 00000000000..614c56b845d --- /dev/null +++ b/Database/CoolRunQuery/share/CoolRunQueryWrapper47.sh @@ -0,0 +1,37 @@ +export PATH=\ +/afs/cern.ch/sw/lcg/external/Python/2.6.5/i686-slc5-gcc43-opt/bin:\ +$PATH + +export ROOTSYS=/data/atrqadm/sw/root + +export PYTHONPATH=`pwd`:\ +/data/atrqadm/sw/InstallArea/python:\ +/afs/cern.ch/atlas/software/builds/AtlasConditions/16.0.1/InstallArea/python:\ +/afs/cern.ch/atlas/software/builds/AtlasCore/16.0.1/InstallArea/i686-slc5-gcc43-opt/lib/python2.6:\ +/afs/cern.ch/atlas/software/builds/AtlasCore/16.0.1/InstallArea/python:\ +/afs/cern.ch/atlas/software/builds/AtlasTrigger/16.0.1/InstallArea/python:\ +/afs/cern.ch/atlas/offline/external/ZSI/2.1-a1/lib/python:\ +/afs/cern.ch/sw/lcg/app/releases/COOL/COOL_2_8_7/i686-slc5-gcc43-opt/python:\ +$ROOTSYS/lib:\ +/afs/cern.ch/sw/lcg/external/pytools/1.2_python2.6/i686-slc5-gcc43-opt/lib/python2.6/site-packages + +export LD_LIBRARY_PATH=\ +/data/atrqadm/sw/InstallArea/i686-slc5-gcc43-opt/lib:\ +/afs/cern.ch/atlas/software/builds/DetCommon/16.0.1/InstallArea/i686-slc5-gcc43-opt/lib:\ +/afs/cern.ch/atlas/offline/external/LCGCMT/LCGCMT_59a/InstallArea/i686-slc5-gcc43-opt/lib:\ +/afs/cern.ch/sw/lcg/external/Python/2.6.5/i686-slc5-gcc43-opt/lib:\ +/afs/cern.ch/sw/lcg/external/gcc/4.3.2/x86_64-slc5/lib:\ +/usr/lib + +export CORAL_DBLOOKUP_PATH=/data/atrqadm/authentication +export CORAL_AUTH_PATH=/data/atrqadm/authentication + +export TNS_ADMIN=/afs/cern.ch/sw/lcg/external/oracle/11.2.0.1.0p2/admin +export HOSTNAME=$HOSTNAME + +export NLS_LANG=AMERICAN_AMERICA.WE8ISO8859P1 + +export COOL_DISABLE_CORALCONNECTIONPOOLCLEANUP=YES + +`pwd`/share/AtlRunQuery.py $* + diff --git a/Database/CoolRunQuery/share/easycommit b/Database/CoolRunQuery/share/easycommit new file mode 100755 index 00000000000..bd9b35b235d --- /dev/null +++ b/Database/CoolRunQuery/share/easycommit @@ -0,0 +1,124 @@ +#!/usr/bin/env python + +#from subprocess import Popen, PIPE +from commands import getstatusoutput,getoutput +import sys,re + +def updateSNVTagInFile(newtag): + + print "updating html/atlas-runquery.html to tag",newtag + p = re.compile("(^.*)CoolRunQuery-\d\d-\d\d-\d\d(.*$)",re.DOTALL) + out = [] + f = open("html/atlas-runquery.html",'r') + for line in f: + m = p.match(line) + if m: + out += [m.group(1)+newtag+m.group(2)] + else: + out += [line] + f.close() + + f = open("html/atlas-runquery.html",'w') + for l in out: f.write(l) + f.close() + + print "updating python/AtlRunQueryVersion.py to tag",newtag + f = open("python/AtlRunQueryVersion.py",'w') + f.write("SvnVersion = '%s'" % newtag) + f.close() + + + +# get package URL +#info = Popen("svn info".split(),stdout=PIPE).communicate()[0].splitlines() +url = getoutput("svn info | grep '^URL'").splitlines()[0] +p = re.compile("URL: (.*)/(trunk)/?()(.*)") +p2 = re.compile("URL: (.*)/(tags)/([^/]+)/{0,1}(.*)") +p3 = re.compile("URL: (.*)/(branches)/([^/]+)/{0,1}(.*)") +pkgurl = None + +m=p.match(url) +if not m: m=p2.match(url) +if not m: m=p3.match(url) + +if not m: + print "Must be in the package directory" + sys.exit(1) + +pkgurl, what, tag, subdir = m.groups() + +if subdir: + print "Must be in the package directory and not in a sub-directory" + sys.exit(1) + +if what=='tags': + print "Must be in the trunk or a branch" + sys.exit(1) + +# find the last +if what=='branches': + branchbase = tag[:tag.rfind('-')] + cmd = "svn list %s/tags | grep %s" % (pkgurl, branchbase) +else: + cmd = "svn list %s/tags" % pkgurl + +tags = getoutput(cmd).splitlines() +if what=='branches': print "Last five branch tags are" +else: print "Last five tags are" +for l in tags[-5:]: + print " "+ l.rstrip('/') + +last = tags[-1].rstrip('/') + +# new tag suggestion +if what=='branches' and last.count('-')==3: + newtag = branchbase+'-01' +else: + newtag = '' + ls = last.split('-') + nti = int(''.join(ls[1:]))+1 + for x in range(len(ls)-1): + nti % 100 + newtag = ("-%02i" % (nti%100)) + newtag + nti /= 100 + newtag = ls[0] + newtag + +# extract comment from Changelog +FH=open("ChangeLog") +FH.readline() +comment=FH.readline().strip().strip('*').strip() +comment.replace("'","") +FH.close() + +# edit +updateSNVTagInFile(newtag) + +print "Comment: '%s'" % comment +answer = raw_input("Would you like to edit html, commit and tag as %s? [Y/n]> " % newtag) + +cmdci = "svn ci -m '%s'" % comment + +if what=='trunk': + cmdtag = "svn cp -m '%s' %s/trunk %s/tags/%s" % (comment,pkgurl,pkgurl,newtag) +else: + cmdtag = "svn cp -m '%s' %s/branches/%s %s/tags/%s" % (comment,pkgurl,tag,pkgurl,newtag) + + +if answer.lower() in 'y': + + # commit to head or branch + print "commiting ..." + status,output = getstatusoutput(cmdci) + print output + if status==0: + # copy to tag + print "tagging ..." + print getoutput(cmdtag) + print "Package tagged as '%s'" % newtag + else: + print "error during commit, will not tag" +else: + print "here some help:" + print cmdci + print cmdtag + diff --git a/Database/CoolRunQuery/share/grepfile.py b/Database/CoolRunQuery/share/grepfile.py new file mode 100644 index 00000000000..f964e039539 --- /dev/null +++ b/Database/CoolRunQuery/share/grepfile.py @@ -0,0 +1,95 @@ +#!/bin/env python2.5 +# +# ---------------------------------------------------------------- +# Script : subproc.py +# Project: AtlRunQuery +# Purpose: Grep lines in file and paste into shtml +# Authors: Andreas Hoecker (CERN), Joerg Stelzer (DESY) +# Created: Nov 13, 2009 +# ---------------------------------------------------------------- +# +import os +import sys +import datetime +import time + +def durationstr(sec): + dt = time.gmtime(sec)[2:6] + if dt[0]>1: + return "%id %ih %im %is" % ((dt[0]-1,) + dt[1:]) + else: + return "%ih %im %is" % dt[1:] + +if __name__ == '__main__': + + # note, it is assumed that the file exist and that exactly 2 argumentds are given + d, searchstr, age, reffile = sys.argv + + # search for dataset pattern + os.system('grep %s %s > data/%s.tmp.txt' % (searchstr, reffile, searchstr)) + + # read file and insert + fr = open( 'data/%s.tmp.txt' % searchstr, "r" ) + ic = 0 + filenames = [] + paths = [] + for line in fr: + ic += 1 + filename, sep, info = str(line).partition(' ') + # remove path - caution, different for RAW files + if '.RAW' in filename: + path = filename[:filename.index(searchstr)] + if not path in paths: paths.append(path) + if len(paths) == 1: + filenames.append( [path, filename[filename.index(searchstr):]] ) + else: + filenames.append( [path, filename] ) + else: + path = filename[:filename.index(searchstr)+len(searchstr)+1] + if not path in paths: paths.append(path) + if len(paths) == 1: + filenames.append( [path, filename[filename.index(searchstr)+len(searchstr)+1:]] ) + else: + filenames.append( [path, filename] ) + fr.close() + + content = '' + if ic>0: + refpath = paths[0] + for path, filename in filenames: + if path == refpath: + content += '<tr><td style="padding-top:5px;padding-bottom:5px;">%s<br>\n' % filename + else: + content += '<tr><td style="padding-top:5px;padding-bottom:5px;"><font color="#CC0000"><i>The path for the following file differs from the above default. Full path + filename is thus given:</i></font><br>\n' + content += '%s<br>\n' % filename + content += '<font color="#999999"><font size="1px">%s</font></font><br></td></tr>\n' % info + + # page header + s = '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html xmlns:"my"><head><head><title>ATLCAL results for dataset: %s' % searchstr + s += '</title><LINK href="atlas-runquery.css" rel="stylesheet" type="text/css"></head><body>\n\n' + s += '<table style="font-family:sans-serif;padding:4px;font-size:80%;width:100%">' + + # body + s += '<tr><td style="font-size:110%%"><b>ATLCAL disk pool − files belonging to dataset: <font color="#CC0000">%s</font><br><hr color="gray" size=1></td></tr>\n' % searchstr + try: + s += '<tr><td>Age of dataset: <b>%s</b></td></tr>' % durationstr(float(age)) + except ValueError: + s += '<tr><td>Age of dataset: <b>%s</b></td></tr>' % 'unknown' + pass + if ic == 0: s += '<tr><td><b>No files found on ATLCAL.</b><br> <br>Note that the accounting of newly replicated datasets may take up to 24h (which adds to the production and replication delay). </td></tr>' + else: s += '<tr><td>Number of files found: <b>%i</b></td></tr>' % ic + + if ic>0: + if len(paths)==1: + s += '<tr><td>Common path for all files:<br> <font color="#AA0000">%s</font><br><hr color="gray" size=1></td></tr>' % refpath + else: + s += '<tr><td>Common path for the following files if not indicated otherwise:<br> <font color="#AA0000">%s</font><br><hr color="gray" size=1></td></tr>' % refpath + + # add the content + s += content + + # end of page + s += '<tr><td><hr color="red" size=1><font color="#777777"><font size="-1"><i><font size="-2">Created by AtlRunQuery on: %s</font></i></font></td></tr>' % str(datetime.datetime.now()) + s += '</table></body></html>' + + os.system("echo '%s' > data/%s.html" % (s, searchstr)) diff --git a/Database/CoolRunQuery/share/query.py b/Database/CoolRunQuery/share/query.py new file mode 100644 index 00000000000..ef51a03f225 --- /dev/null +++ b/Database/CoolRunQuery/share/query.py @@ -0,0 +1,114 @@ +#!/bin/env python2.5 +#$Name: not supported by cvs2svn $ + +""" Publisher example """ +import sys, re, time, datetime, os +from random import choice + +def index(q=''): + + if q.startswith('arq_'): + (page, fullpath) = _cache_request(q) + else: + (page, fullpath) = _new_request(q) + + _touch(fullpath) + + return page + +def _new_request(q=''): + installpath = os.path.dirname(__file__) + + timeofrequest = time.gmtime() + + # data stream uses current time + queryday = time.strftime("%y%m%d",timeofrequest) + queryid = time.strftime("%y%m%d%H%M%S",timeofrequest) + + # add random string to avoid conflict from coincident query + queryid += "".join([choice('abcdefghijklmnopqrstyz') for x in xrange(4)]) + + # the query datapath + datapath = 'data/arq_%s/arq_%s' % (queryday,queryid) + + # where to store the result + fulldatapath = '%s/%s' % (installpath,datapath) + os.makedirs(fulldatapath) + + # the query that should be run + queryfile = '%s/query.txt' % fulldatapath + fh = open(queryfile,"w") + print >> fh, "%s" % q + fh.close() + + # global log file + logfile = '%s/data/log.txt' % installpath + fh = open(logfile,"a") + print >> fh, "%s / [id %s] - received query: %s" % (timeofrequest, queryid, q if q else "none" ) + fh.close() + + com = "cd %s; ./CoolRunQueryWrapper.sh fileindex %s" % (installpath,queryid) + + # run the query + from commands import getoutput + log = getoutput(com) + logfile = '%s/log.txt' % fulldatapath + fh = open(logfile,"w") + print >> fh, log + fh.close() + + + # forward file + outputfile = '%s/index.html' % fulldatapath + try: + fh = open(outputfile,"r") + page = fh.read() + fh.close() + if not page.rstrip().endswith("</html>"): + page = _error_page(datapath) + except IOError: + page = "<html><body>No web page created! Here the log file:<pre><br><br><br>%s</pre></body></html>" % (log.replace("<","<").replace(">",">")) + + return (page,fulldatapath) + + + +def _error_page(datapath): + + s = """<html> + <head><title>Error</title></head> + <body> + Found incomplete web page! Would you like to see the + <a target="_blank" href="query.py?q=%s">web page fragment</a> or the + <a target="_blank" href="%s/log.txt">log file</a> ? + </body> + </html> + """ % (datapath.split('/')[-1], datapath) + return s + + + +def _cache_request(q): + installpath = os.path.dirname(__file__) + fulldatapath = '%s/data/%s/%s' % (installpath,q[:10],q) + try: + # open cache + fh = open('%s/index.html' % fulldatapath,"r") + page = fh.read() + fh.close() + return (page,fulldatapath) + except IOError: + return ("Could not find cache %s" % q, None) + +def _touch(fullpath): + if fullpath==None: return + installpath = os.path.dirname(__file__) + try: + # open cache + fh = open('%s/access.log' % fullpath,"a") + timeofaccess = time.gmtime() + querytime = time.strftime("%y%m%d",timeofaccess) + print >> fh, querytime + fh.close() + except IOError: + pass diff --git a/Database/CoolRunQuery/share/subproc.py b/Database/CoolRunQuery/share/subproc.py new file mode 100644 index 00000000000..927c4220b1a --- /dev/null +++ b/Database/CoolRunQuery/share/subproc.py @@ -0,0 +1,37 @@ +#!/bin/env python2.5 +# +# ---------------------------------------------------------------- +# Script : subproc.py +# Project: AtlRunQuery +# Purpose: Executable for python subprocesses +# Authors: Andreas Hoecker (CERN), Joerg Stelzer (DESY) +# Created: Nov 12, 2009 +# ---------------------------------------------------------------- +# + +import subprocess +import time +import sys +import os.path + +if __name__ == '__main__': + + subtype = 'caf' + argstr = '' + if len(sys.argv)>1: subtype = sys.argv[1] + if len(sys.argv)>2: argstr = sys.argv[2] + + # start python subprocess to find files on CAF --------- + # first check age of reference file, reload if older than 1d + if subtype == 'caf': + reffile = 'data/atlas.atlcal.last' + if not os.path.isfile(reffile) or (time.time()-os.path.getmtime(reffile))/3600.>24.: + subprocess.Popen('wget -o data/wget.log http://castor.web.cern.ch/castor/DiskPoolDump/atlas.atlcal.last -O %s' % (reffile), shell=True).wait() + + dsnamesAndAge = argstr.split(',') + agrs = [] + for dsnameAndAge in dsnamesAndAge: + # format is: '<dsname>@<age_in_sec>' + dsname, sep, age = dsnameAndAge.partition('@') + subprocess.Popen('python2.5 grepfile.py %s %s %s' % (dsname, age, reffile), shell=True) + -- GitLab