From b4ed2ac5007a069cc9f0777a230f799eec37c631 Mon Sep 17 00:00:00 2001 From: Harris Tzovanakis Date: Mon, 24 Aug 2015 15:19:18 +0200 Subject: [PATCH] webstat: downloads and pageviews as events * IMPROVEMENT Adds `downloads` and `pagesviews` as custom events. * Custom events will be registered only in elasticsearch if `CFG_ELASTICSEARCH_LOGGING` is enabled. Signed-off-by: Harris Tzovanakis --- modules/bibdocfile/lib/bibdocfile.py | 46 ++++------ .../lib/bibrank_downloads_similarity.py | 48 +++++----- .../invenio_2015_08_24_custom_events.py | 66 +++++++++++++ modules/webstat/etc/webstat.cfg | 92 +++++++++++++------ modules/webstat/lib/webstat.py | 75 +++++++-------- modules/webstat/lib/webstat_config.py | 69 +++++++------- modules/webstat/lib/webstat_templates.py | 49 +++++----- modules/webstat/lib/webstatadmin.py | 40 ++++---- 8 files changed, 291 insertions(+), 194 deletions(-) create mode 100644 modules/miscutil/lib/upgrades/invenio_2015_08_24_custom_events.py diff --git a/modules/bibdocfile/lib/bibdocfile.py b/modules/bibdocfile/lib/bibdocfile.py index 54afafaa39..0f0e6755ca 100644 --- a/modules/bibdocfile/lib/bibdocfile.py +++ b/modules/bibdocfile/lib/bibdocfile.py @@ -1,5 +1,5 @@ # This file is part of Invenio. -# Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 CERN. +# Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 CERN. # # Invenio is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -95,6 +95,7 @@ from sets import Set as set # pylint: enable=W0622 +#from invenio.webstat import register_customevent from invenio.shellutils import escape_shell_arg, run_shell_command from invenio.dbquery import run_sql, DatabaseError from invenio.errorlib import register_exception @@ -123,7 +124,6 @@ CFG_BIBDOCFILE_ADDITIONAL_KNOWN_MIMETYPES, \ CFG_BIBDOCFILE_PREFERRED_MIMETYPES_MAPPING, \ CFG_BIBCATALOG_SYSTEM, \ - CFG_ELASTICSEARCH_LOGGING, \ CFG_ELASTICSEARCH_BOT_AGENT_STRINGS from invenio.bibcatalog import BIBCATALOG_SYSTEM from invenio.bibdocfile_config import CFG_BIBDOCFILE_ICON_SUBFORMAT_RE, \ @@ -132,10 +132,6 @@ import invenio.template -if CFG_ELASTICSEARCH_LOGGING: - import logging - - _DOWNLOAD_LOG = logging.getLogger('events.downloads') def _plugin_bldr(dummy, plugin_code): """Preparing the plugin dictionary structure""" @@ -2983,29 +2979,27 @@ def register_download(self, ip_address, version, docformat, user_agent, docformat = docformat.upper() if not version: version = self.get_latest_version() - if CFG_ELASTICSEARCH_LOGGING: - log_entry = { - 'id_bibrec': recid, - 'id_bibdoc': self.id, - 'file_version': version, - 'file_format': docformat, - 'id_user': userid, - 'client_host': ip_address, - 'user_agent': user_agent - } - if user_agent is not None: + + try: + from invenio.webstat import register_customevent + ## register event in webstat + download_register_event = [ + recid, self.id, version, docformat, userid, ip_address, + user_agent + ] + is_bot = False + if user_agent: for bot in CFG_ELASTICSEARCH_BOT_AGENT_STRINGS: if bot in user_agent: - log_entry['bot'] = True + is_bot = True break - _DOWNLOAD_LOG.info(log_entry) - else: - return run_sql("INSERT INTO rnkDOWNLOADS " - "(id_bibrec,id_bibdoc,file_version,file_format," - "id_user,client_host,download_time) VALUES " - "(%s,%s,%s,%s,%s,INET_ATON(%s),NOW())", - (recid, self.id, version, docformat, - userid, ip_address,)) + download_register_event.append(is_bot) + register_customevent("downloads", download_register_event) + except: + register_exception( + ("Do the webstat tables exists? Try with 'webstatadmin" + " --load-config'") + ) def get_incoming_relations(self, rel_type=None): """Return all relations in which this BibDoc appears on target position diff --git a/modules/bibrank/lib/bibrank_downloads_similarity.py b/modules/bibrank/lib/bibrank_downloads_similarity.py index 568cd0261b..46e8c49255 100644 --- a/modules/bibrank/lib/bibrank_downloads_similarity.py +++ b/modules/bibrank/lib/bibrank_downloads_similarity.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # This file is part of Invenio. -# Copyright (C) 2005, 2006, 2007, 2008, 2010, 2011, 2012 CERN. +# Copyright (C) 2005, 2006, 2007, 2008, 2010, 2011, 2012, 2015 CERN. # # Invenio is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -20,19 +20,15 @@ __revision__ = \ "$Id$" -from invenio.config import \ - CFG_ACCESS_CONTROL_LEVEL_SITE, \ - CFG_CERN_SITE, \ - CFG_ELASTICSEARCH_LOGGING, \ - CFG_ELASTICSEARCH_BOT_AGENT_STRINGS +from invenio.config import ( + CFG_ACCESS_CONTROL_LEVEL_SITE, CFG_CERN_SITE, + CFG_ELASTICSEARCH_BOT_AGENT_STRINGS +) from invenio.dbquery import run_sql from invenio.bibrank_downloads_indexer import database_tuples_to_single_list from invenio.search_engine_utils import get_fieldvalues +from invenio.errorlib import register_exception -if CFG_ELASTICSEARCH_LOGGING: - import logging - - _PAGEVIEW_LOG = logging.getLogger('events.pageviews') def record_exists(recID): """Return 1 if record RECID exists. @@ -62,24 +58,26 @@ def register_page_view_event(recid, uid, client_ip_address, user_agent): # do not register access if we are in read-only access control # site mode: return [] - if CFG_ELASTICSEARCH_LOGGING: - log_event = { - 'id_bibrec': recid, - 'id_user': uid, - 'client_host': client_ip_address, - 'user_agent': user_agent - } - if user_agent is not None: + + # register event in webstat + try: + from invenio.webstat import register_customevent + pageviews_register_event = [ + recid, uid, client_ip_address, user_agent + ] + is_bot = False + if user_agent: for bot in CFG_ELASTICSEARCH_BOT_AGENT_STRINGS: if bot in user_agent: - log_event['bot'] = True + is_bot = True break - _PAGEVIEW_LOG.info(log_event) - else: - return run_sql("INSERT INTO rnkPAGEVIEWS " \ - " (id_bibrec,id_user,client_host,view_time) " \ - " VALUES (%s,%s,INET_ATON(%s),NOW())", \ - (recid, uid, client_ip_address)) + pageviews_register_event.append(is_bot) + register_customevent("pageviews", pageviews_register_event) + except: + register_exception( + ("Do the webstat tables exists? Try with 'webstatadmin" + " --load-config'") + ) def calculate_reading_similarity_list(recid, type="pageviews"): """Calculate reading similarity data to use in reading similarity diff --git a/modules/miscutil/lib/upgrades/invenio_2015_08_24_custom_events.py b/modules/miscutil/lib/upgrades/invenio_2015_08_24_custom_events.py new file mode 100644 index 0000000000..accc436cd1 --- /dev/null +++ b/modules/miscutil/lib/upgrades/invenio_2015_08_24_custom_events.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Invenio. +# Copyright (C) 2015 CERN. +# +# Invenio is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# Invenio is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Invenio; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + +"""Add downloads and pageviews as custom events.""" + +from invenio.webstat import create_customevent + +depends_on = ['invenio_release_1_3_0'] + + +def info(): + """Return upgrade recipe information.""" + return "Adds downloads and pageviews as custom events." + + +def do_upgrade(): + """Carry out the upgrade.""" + # create the downloads + create_customevent( + event_id="downloads", + name="downloads", + cols=[ + "id_bibrec", "id_bibdoc", "file_version", "file_format", + "id_user", "client_host", "user_agent", "bot" + ] + ) + # create the pageviews + create_customevent( + event_id="pageviews", + name="pageviews", + cols=[ + "id_bibrec", "id_user", "client_host", "user_agent", "bot" + ] + ) + return 1 + + +def estimate(): + """Estimate running time of upgrade in seconds (optional).""" + return 1 + + +def pre_upgrade(): + """Pre-upgrade checks.""" + pass # because slashes would still work + + +def post_upgrade(): + """Post-upgrade checks.""" + pass diff --git a/modules/webstat/etc/webstat.cfg b/modules/webstat/etc/webstat.cfg index 92c3170d1c..9e31615acd 100644 --- a/modules/webstat/etc/webstat.cfg +++ b/modules/webstat/etc/webstat.cfg @@ -4,14 +4,14 @@ [general] visitors_box = True search_box = True -record_box = True -bibsched_box = True +record_box = False +bibsched_box = False basket_box = True alert_box = True -loan_box = True apache_box = True uptime_box = True -waiting_box = True +loan_box = True +waiting_box = False max_ingestion_health = 25 [webstat_custom_event_1] @@ -27,43 +27,79 @@ param2 = alert param3 = user [webstat_custom_event_3] -name = journals -param1 = action -param2 = journal_name -param3 = issue_number -param4 = category -param5 = language -param6 = articleid +name = media_download +param1 = file +param2 = type +param3 = format +param4 = ip [webstat_custom_event_4] +name = ejournal +param1 = publication +param2 = volume +param3 = year +param4 = page +param5 = source +param6 = ip + +[webstat_custom_event_5] +name = media_view +param1 = file +param2 = type +param3 = format +param4 = ip +param5 = external +param6 = recid + +[webstat_custom_event_6] name = websubmissions param1 = doctype -[webstat_custom_event_5] +[webstat_custom_event_7] name = loanrequest param1 = request_id param2 = loan_id -[webstat_custom_event_6] -name = login -param1 = IP -param2 = UID -param3 = email +[webstat_custom_event_8] +name = journals +param1 = action +param2 = journal_name +param3 = issue_number +param4 = category +param5 = language +param6 = articleid -[webstat_custom_event_7] -name = apikeyusage +[webstat_custom_event_9] +name = ebidding_opening param1 = user_id -param2 = key_id -param3 = path -param4 = query +param2 = etendering_ref +param3 = action + +[webstat_custom_event_10] +name = downloads +param1 = id_bibrec +param2 = id_bibdoc +param3 = file_version +param4 = file_format +param5 = id_user +param6 = client_host +param7 = user_agent +param7 = bot + +[webstat_custom_event_11] +name = pageviews +param1 = id_bibrec +param2 = id_user +param3 = client_host +param4 = user_agent +param5 = bot [apache_log_analyzer] profile = nil nb-histogram-items-to-print = 20 -exclude-ip-list = ("137.138.249.162" "137.138.246.86") -home-collection = "Atlantis Institute of Fictive Science" -search-interface-url = "/collection/" -search-interface-url-old-style = "/?" +exclude-ip-list = '("137.138.198.205") +home-collection = "CERN Document Server" +search-interface-url = "/?" detailed-record-url = "/record/" search-engine-url = "/search?" search-engine-url-old-style = "/search.py?" @@ -72,5 +108,5 @@ add-to-basket-url = "/yourbaskets/add" display-basket-url = "/yourbaskets/display" display-public-basket-url = "/yourbaskets/display_public" alert-url = "/youralerts/" -display-your-alerts-url = "/youralerts/display" -display-your-searches-url = "/yoursearches/display" +display-your-alerts-url = "/youralerts/list" +display-your-searches-url = "/youralerts/display" diff --git a/modules/webstat/lib/webstat.py b/modules/webstat/lib/webstat.py index de7b771346..9f0316da4b 100644 --- a/modules/webstat/lib/webstat.py +++ b/modules/webstat/lib/webstat.py @@ -1,19 +1,19 @@ -# This file is part of Invenio. -# Copyright (C) 2007, 2008, 2009, 2010, 2011, 2013 CERN. -# -# Invenio is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 2 of the -# License, or (at your option) any later version. -# -# Invenio is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Invenio; if not, write to the Free Software Foundation, Inc., -# 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +## This file is part of Invenio. +## Copyright (C) 2007, 2008, 2009, 2010, 2011, 2013 CERN. +## +## Invenio is free software; you can redistribute it and/or +## modify it under the terms of the GNU General Public License as +## published by the Free Software Foundation; either version 2 of the +## License, or (at your option) any later version. +## +## Invenio is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Invenio; if not, write to the Free Software Foundation, Inc., +## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. __revision__ = "$Id$" __lastupdated__ = "$Date$" @@ -796,31 +796,14 @@ def register_customevent(event_id, *arguments): # Make sql query if len(arguments[0]) != 0: - sql_param = [] - sql_query = ["INSERT INTO %s (" % wash_table_column_name(tbl_name)] - for title in col_titles: - sql_query.append("`%s`" % title) - sql_query.append(",") - # del the last ',' - sql_query.pop() - sql_query.append(") VALUES (") - for argument in arguments[0]: - sql_query.append("%s") - sql_query.append(",") - sql_param.append(argument) - # del the last ',' - sql_query.pop() - sql_query.append(")") - sql_str = ''.join(sql_query) - run_sql(sql_str, tuple(sql_param)) - + # Register the event on elastic search ONLY! + elastic_search_event_id = "events.{0}".format(event_id) # Register the event on elastic search - if CFG_ELASTICSEARCH_LOGGING and event_id in \ + if CFG_ELASTICSEARCH_LOGGING and elastic_search_event_id in \ CFG_ELASTICSEARCH_EVENTS_MAP.keys(): # Initialize elastic search handler elastic_search_parameters = zip(col_titles, arguments[0]) - event_logger_name = "events.{0}".format(event_id) - logger = getLogger(event_logger_name) + logger = getLogger(elastic_search_event_id) log_event = {} for key, value in elastic_search_parameters: log_event[key] = value @@ -829,6 +812,24 @@ def register_customevent(event_id, *arguments): else: postprocessors = [] logger.info(log_event, {'postprocessors': postprocessors}) + else: + sql_param = [] + sql_query = ["INSERT INTO %s (" % wash_table_column_name(tbl_name)] + for title in col_titles: + sql_query.append("`%s`" % title) + sql_query.append(",") + # del the last ',' + sql_query.pop() + sql_query.append(") VALUES (") + for argument in arguments[0]: + sql_query.append("%s") + sql_query.append(",") + sql_param.append(argument) + # del the last ',' + sql_query.pop() + sql_query.append(")") + sql_str = ''.join(sql_query) + run_sql(sql_str, tuple(sql_param)) else: # kwalitee: disable=sql run_sql( diff --git a/modules/webstat/lib/webstat_config.py b/modules/webstat/lib/webstat_config.py index da93dc732d..363d084278 100644 --- a/modules/webstat/lib/webstat_config.py +++ b/modules/webstat/lib/webstat_config.py @@ -1,50 +1,47 @@ # -*- coding: utf-8 -*- -# -# This file is part of Invenio. -# Copyright (C) 2008, 2010, 2011 CERN. -# -# Invenio is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 2 of the -# License, or (at your option) any later version. -# -# Invenio is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Invenio; if not, write to the Free Software Foundation, Inc., -# 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +## +## This file is part of Invenio. +## Copyright (C) 2008, 2010, 2011, 2015 CERN. +## +## Invenio is free software; you can redistribute it and/or +## modify it under the terms of the GNU General Public License as +## published by the Free Software Foundation; either version 2 of the +## License, or (at your option) any later version. +## +## Invenio is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Invenio; if not, write to the Free Software Foundation, Inc., +## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """Invenio WebStat Configuration.""" __revision__ = "$Id$" -from invenio.config import CFG_ETCDIR, CFG_ELASTICSEARCH_LOGGING, CFG_CERN_SITE +from invenio.config import ( + CFG_ETCDIR, + CFG_ELASTICSEARCH_LOGGING, + CFG_CERN_SITE +) CFG_WEBSTAT_CONFIG_PATH = CFG_ETCDIR + "/webstat/webstat.cfg" if CFG_CERN_SITE: CFG_ELASTICSEARCH_EVENTS_MAP = { - "events.loanrequest": { - "_source": { - "enabled": True - }, - "properties": { - "request_id": { - "type": "integer" - }, - "load_id": { - "type": "integer" - } - } - } + "events.alerts": True, + "events.baskets": True, + "events.downloads": True, + "events.ebidding_opening": True, + "events.ejournal": True, + "events.journals": True, + "events.loanrequest": True, + "events.media_download": True, + "events.media_view": True, + "events.pageviews": True, + "events.websubmissions": True, } else: CFG_ELASTICSEARCH_EVENTS_MAP = {} - -if CFG_ELASTICSEARCH_LOGGING: - from invenio.elasticsearch_logging import register_schema - for name, arguments in CFG_ELASTICSEARCH_EVENTS_MAP.items(): - register_schema(name, arguments) diff --git a/modules/webstat/lib/webstat_templates.py b/modules/webstat/lib/webstat_templates.py index 83250384e9..f64c730276 100644 --- a/modules/webstat/lib/webstat_templates.py +++ b/modules/webstat/lib/webstat_templates.py @@ -1,30 +1,29 @@ -# This file is part of Invenio. -# Copyright (C) 2007, 2008, 2010, 2011, 2013 CERN. -# -# Invenio is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 2 of the -# License, or (at your option) any later version. -# -# Invenio is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Invenio; if not, write to the Free Software Foundation, Inc., -# 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +## This file is part of Invenio. +## Copyright (C) 2007, 2008, 2010, 2011, 2013, 2015 CERN. +## +## Invenio is free software; you can redistribute it and/or +## modify it under the terms of the GNU General Public License as +## published by the Free Software Foundation; either version 2 of the +## License, or (at your option) any later version. +## +## Invenio is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Invenio; if not, write to the Free Software Foundation, Inc., +## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. __revision__ = "$Id$" __lastupdated__ = "$Date$" import datetime, cgi, urllib, os import re -from invenio.config import \ - CFG_WEBDIR, \ - CFG_SITE_URL, \ - CFG_SITE_LANG, \ - CFG_SITE_NAME +from invenio.config import ( + CFG_WEBDIR, CFG_SITE_URL, CFG_SITE_LANG, CFG_SITE_NAME, + CFG_ELASTICSEARCH_LOGGING +) from invenio.search_engine import get_coll_sons from invenio.webstat_engine import get_invenio_error_details @@ -148,7 +147,13 @@ def tmpl_customevent_list(self, customevents, ln=CFG_SITE_LANG): When a custom event has been made available, it is displayed below.

""" % CFG_SITE_URL - + # If elasticsearch is enabled display the message + if CFG_ELASTICSEARCH_LOGGING: + message = ( + "Elastic search logging for custom events has been enabled " + "the custom events will no longer been displayed here." + ) + return out + message temp_out = "" for event in customevents: temp_out += """
  • %s
  • """ \ diff --git a/modules/webstat/lib/webstatadmin.py b/modules/webstat/lib/webstatadmin.py index cb753482bb..534092b73f 100644 --- a/modules/webstat/lib/webstatadmin.py +++ b/modules/webstat/lib/webstatadmin.py @@ -1,21 +1,21 @@ -# $id: webstatadmin.py,v 1.28 2007/04/01 23:46:46 tibor exp $ -# -# This file is part of Invenio. -# Copyright (C) 2007, 2008, 2010, 2011 CERN. -# -# Invenio is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as -# published by the Free Software Foundation; either version 2 of the -# License, or (at your option) any later version. -# -# Invenio is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Invenio; if not, write to the Free Software Foundation, Inc., -# 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +## $id: webstatadmin.py,v 1.28 2007/04/01 23:46:46 tibor exp $ +## +## This file is part of Invenio. +## Copyright (C) 2007, 2008, 2010, 2011 CERN. +## +## Invenio is free software; you can redistribute it and/or +## modify it under the terms of the GNU General Public License as +## published by the Free Software Foundation; either version 2 of the +## License, or (at your option) any later version. +## +## Invenio is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Invenio; if not, write to the Free Software Foundation, Inc., +## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. __revision__ = "$Id$" __lastupdated__ = "$Date$" @@ -181,8 +181,8 @@ def task_submit_check_options(): display-basket-url = "/yourbaskets/display" display-public-basket-url = "/yourbaskets/display_public" alert-url = "/youralerts/" -display-your-alerts-url = "/youralerts/display" -display-your-searches-url = "/yoursearches/display" +display-your-alerts-url = "/youralerts/list" +display-your-searches-url = "/youralerts/display" """ % CFG_SITE_RECORD sys.exit(0)