Skip to content

Commit

Permalink
Fixes bug 882126 - New style signature_history service.
Browse files Browse the repository at this point in the history
  • Loading branch information
adngdb committed Jul 10, 2013
1 parent a2a8e25 commit 58e1d25
Show file tree
Hide file tree
Showing 6 changed files with 298 additions and 22 deletions.
72 changes: 72 additions & 0 deletions docs/middleware.rst
Expand Up @@ -735,6 +735,78 @@ Return an object like the following::
}


.. ############################################################################
Crashes Signatures API
############################################################################
Crashes Signatures
------------------

Return the history of a signature.

API specifications
^^^^^^^^^^^^^^^^^^

+----------------+--------------------------------------------------------------------------------+
| HTTP method | GET |
+----------------+--------------------------------------------------------------------------------+
| URL schema | /crashes/signature_history/(optional_parameters) |
+----------------+--------------------------------------------------------------------------------+
| Full URL | /crashes/signature_history/product/(product)/version/(version)/ |
| | start_date/(start_date)/end_date/(end_date)/signature/(signature)/ |
+----------------+--------------------------------------------------------------------------------+
| Example | http://socorro-api/bpapi/crashes/signature_history/product/Firefox/ |
| | signature/my_signature_rocks/ |
+----------------+--------------------------------------------------------------------------------+

Mandatory parameters
^^^^^^^^^^^^^^^^^^^^

+------------+---------------+------------------------------------------------+
| Name | Type of value | Description |
+============+===============+================================================+
| product | String | Name of the product. |
+------------+---------------+------------------------------------------------+
| version | String | Number of the version. |
+------------+---------------+------------------------------------------------+
| signature | String | Signature to get, exact match. |
+------------+---------------+------------------------------------------------+

Optional parameters
^^^^^^^^^^^^^^^^^^^

+-----------------+---------------+---------------+--------------------------------+
| Name | Type of value | Default value | Description |
+=================+===============+===============+================================+
| start_date | Datetime | Last week | The earliest date of crashes |
| | | | we wish to evaluate |
+-----------------+---------------+---------------+--------------------------------+
| end_date | Datetime | Now | The latest date of crashes we |
| | | | wish to evaluate. |
+-----------------+---------------+---------------+--------------------------------+

Return value
^^^^^^^^^^^^

Return an object like the following::

{
"hits": [
{
"date": "2012-03-13",
"count": 3,
"percent_of_total": 42
},
{
"date": "2012-03-20",
"count": 6,
"percent_of_total": 76
}
],
"total": 2
}


.. ############################################################################
Crashes Exploitability API
############################################################################
Expand Down
2 changes: 2 additions & 0 deletions scripts/config/webapiconfig.py.dist
Expand Up @@ -166,6 +166,7 @@ import socorro.middleware.crontabber_state_service as crontabber_state
import socorro.middleware.correlations_service as correlations
import socorro.middleware.field_service as field
import socorro.middleware.crashes_exploitability_service as crashes_exploitability
import socorro.middleware.crashes_signature_history_service as crashes_signature_history

servicesList = cm.Option()
servicesList.doc = 'a python list of classes to offer as services'
Expand Down Expand Up @@ -206,6 +207,7 @@ servicesList.default = [
correlations.Correlations,
field.Field,
crashes_exploitability.CrashesExploitability,
crashes_signature_history.CrashesSignatureHistory,
]

crashBaseUrl = cm.Option()
Expand Down
125 changes: 107 additions & 18 deletions socorro/external/postgresql/crashes.py
Expand Up @@ -28,7 +28,8 @@ def prepare_search_params(self, **kwargs):

if not params["signature"]:
raise MissingOrBadArgumentError(
"Mandatory parameter 'signature' is missing or empty")
"Mandatory parameter 'signature' is missing or empty"
)

params["terms"] = params["signature"]
params["search_mode"] = "is_exactly"
Expand All @@ -41,8 +42,9 @@ def prepare_search_params(self, **kwargs):
if params["report_process"] == "plugin" and params["plugin_terms"]:
params["plugin_terms"] = " ".join(params["plugin_terms"])
params["plugin_terms"] = Crashes.prepare_terms(
params["plugin_terms"],
params["plugin_search_mode"])
params["plugin_terms"],
params["plugin_search_mode"]
)

# Get information about the versions
util_service = Util(config=self.context)
Expand All @@ -51,8 +53,9 @@ def prepare_search_params(self, **kwargs):
# Parsing the versions
params["versions_string"] = params["versions"]
(params["versions"], params["products"]) = Crashes.parse_versions(
params["versions"],
params["products"])
params["versions"],
params["products"]
)

# Changing the OS ids to OS names
if hasattr(self.context, 'webapi'):
Expand Down Expand Up @@ -104,8 +107,9 @@ def get_comments(self, **kwargs):

# Assembling the query
sql_query = " ".join((
"/* external.postgresql.crashes.Crashes.get_comments */",
sql_select, sql_from, sql_where, sql_order))
"/* external.postgresql.crashes.Crashes.get_comments */",
sql_select, sql_from, sql_where, sql_order)
)

error_message = "Failed to retrieve comments from PostgreSQL"
results = self.query(sql_query, sql_params,
Expand All @@ -115,12 +119,14 @@ def get_comments(self, **kwargs):
comments = []
for row in results:
comment = dict(zip((
"date_processed",
"user_comments",
"uuid",
"email"), row))
"date_processed",
"user_comments",
"uuid",
"email",
), row))
comment["date_processed"] = datetimeutil.date_to_string(
comment["date_processed"])
comment["date_processed"]
)
comments.append(comment)

return {
Expand Down Expand Up @@ -154,11 +160,13 @@ def get_daily(self, **kwargs):

if not params.product:
raise MissingOrBadArgumentError(
"Mandatory parameter 'product' is missing or empty")
"Mandatory parameter 'product' is missing or empty"
)

if not params.versions or not params.versions[0]:
raise MissingOrBadArgumentError(
"Mandatory parameter 'versions' is missing or empty")
"Mandatory parameter 'versions' is missing or empty"
)

params.versions = tuple(params.versions)

Expand Down Expand Up @@ -279,7 +287,8 @@ def get_daily(self, **kwargs):
daily_data["throttle"] = float(daily_data["throttle"])
daily_data["crash_hadu"] = float(daily_data["crash_hadu"])
daily_data["date"] = datetimeutil.date_to_string(
daily_data["date"])
daily_data["date"]
)

key = "%s:%s" % (daily_data["product"],
daily_data["version"])
Expand Down Expand Up @@ -360,8 +369,9 @@ def get_frequency(self, **kwargs):

# Assembling the query
sql = " ".join((
"/* external.postgresql.crashes.Crashes.get_fequency */",
sql_select, sql_from, sql_where, sql_group, sql_order))
"/* external.postgresql.crashes.Crashes.get_fequency */",
sql_select, sql_from, sql_where, sql_group, sql_order)
)
sql = str(" ".join(sql.split())) # better formatting of the sql string

# Query the database
Expand Down Expand Up @@ -395,7 +405,8 @@ def get_paireduuid(self, **kwargs):

if not params.uuid:
raise MissingOrBadArgumentError(
"Mandatory parameter 'uuid' is missing or empty")
"Mandatory parameter 'uuid' is missing or empty"
)

crash_date = datetimeutil.uuid_to_date(params.uuid)

Expand Down Expand Up @@ -470,6 +481,84 @@ def get_signatures(self, **kwargs):
finally:
connection.close()

def get_signature_history(self, **kwargs):
"""Return the history of a signature.
See http://socorro.readthedocs.org/en/latest/middleware.html#crashes_signature_history
"""
now = datetimeutil.utc_now()
lastweek = now - datetime.timedelta(days=7)

filters = [
('product', None, 'str'),
('version', None, 'str'),
('signature', None, 'str'),
('end_date', now, 'datetime'),
('start_date', lastweek, 'datetime'),
]
params = external_common.parse_arguments(filters, kwargs)

for param in ('product', 'version', 'signature'):
if not params[param]:
raise MissingOrBadArgumentError(
"Mandatory parameter '%s' is missing or empty" % param
)

if params.signature == '##null##':
signature_where = 'AND signature IS NULL'
else:
signature_where = 'AND signature = %(signature)s'

if params.signature == '##empty##':
params.signature = ''

sql = """
/* external.postgresql.crashes.Crashes.get_signature_history */
WITH hist AS (
SELECT
report_date,
report_count
FROM
tcbs JOIN signatures using (signature_id)
JOIN product_versions using (product_version_id)
WHERE
report_date BETWEEN %%(start_date)s AND %%(end_date)s
AND product_name = %%(product)s
AND version_string = %%(version)s
%s
GROUP BY
report_date, report_count
ORDER BY 1
),
scaling_window AS (
SELECT
hist.*,
SUM(report_count) over () AS total_crashes
FROM hist
)
SELECT
report_date,
report_count,
report_count / total_crashes::float * 100 AS percent_of_total
FROM scaling_window
ORDER BY report_date DESC
""" % signature_where

error_message = 'Failed to retrieve signature history from PostgreSQL'
results = self.query(sql, params, error_message=error_message)

# Transforming the results into what we want
history = []
for row in results:
dot = dict(zip(('date', 'count', 'percent_of_total'), row))
dot['date'] = datetimeutil.date_to_string(dot['date'])
history.append(dot)

return {
'hits': history,
'total': len(history)
}

def get_exploitability(self, **kwargs):
"""Return a list of exploitable crash reports.
Expand Down
26 changes: 26 additions & 0 deletions socorro/middleware/crashes_signature_history_service.py
@@ -0,0 +1,26 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import logging

from socorro.middleware.service import DataAPIService

logger = logging.getLogger("webapi")


class CrashesSignatureHistory(DataAPIService):
"""Return the history of a signature. """

service_name = "crashes"
uri = "/crashes/signature_history/(.*)"

def __init__(self, config):
super(CrashesSignatureHistory, self).__init__(config)
logger.debug('CrashesSignatureHistory service __init__')

def get(self, *args):
params = self.parse_query_string(args[0])
module = self.get_module(params)
impl = module.Crashes(config=self.context)
return impl.get_signature_history(**params)
2 changes: 1 addition & 1 deletion socorro/middleware/middleware_app.py
Expand Up @@ -31,7 +31,7 @@
(r'/bugs/(.*)', 'bugs.Bugs'),
(r'/crash_data/(.*)', 'crash_data.CrashData'),
(r'/crash/(.*)', 'crash.Crash'),
(r'/crashes/(comments|daily|frequency|paireduuid|signatures|exploitability)/(.*)',
(r'/crashes/(comments|daily|frequency|paireduuid|signatures|signature_history|exploitability)/(.*)',
'crashes.Crashes'),
(r'/extensions/(.*)', 'extensions.Extensions'),
(r'/field/(.*)', 'field.Field'),
Expand Down

0 comments on commit 58e1d25

Please sign in to comment.