blob: 5fd0bf18685b65297eac3d7f41a3fe86bb33c486 [file] [log] [blame]
# Copyright 2020 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""URL endpoint for getting information about histogram upload proccess."""
from __future__ import absolute_import
import logging
import uuid
from google.appengine.ext import ndb
from dashboard.api import api_request_handler
from dashboard.api import api_auth
from dashboard.common import histogram_helpers
from dashboard.common import utils
from dashboard.models import histogram
from dashboard.models import upload_completion_token
from tracing.value import histogram as histogram_module
from tracing.value.diagnostics import diagnostic as diagnostic_module
from tracing.value.diagnostics import generic_set
from tracing.value.diagnostics import diagnostic_ref
from flask import request
def _IsValidUuid(val):
try:
uuid.UUID(str(val))
return True
except ValueError:
return False
# pylint: disable=abstract-method
def _CheckUser():
if request.remote_addr and request.remote_addr in utils.GetIpAllowlist():
return
if utils.IsDevAppserver():
return
api_auth.Authorize()
if not utils.IsInternalUser():
raise api_request_handler.ForbiddenError()
@ndb.tasklet
def _GenerateMeasurementInfo(measurement, get_dimensions_info=False):
info = {
'name': measurement.test_path,
'state': upload_completion_token.StateToString(measurement.state),
'monitored': measurement.monitored,
'lastUpdated': str(measurement.update_time),
}
if measurement.error_message is not None:
info['error_message'] = measurement.error_message
if get_dimensions_info and measurement.histogram is not None:
histogram_entity = yield measurement.histogram.get_async()
attached_histogram = histogram_module.Histogram.FromDict(
histogram_entity.data)
info['dimensions'] = []
for name, diagnostic in attached_histogram.diagnostics.items():
if name not in histogram_helpers.ADD_HISTOGRAM_RELATED_DIAGNOSTICS:
continue
if isinstance(diagnostic, diagnostic_ref.DiagnosticRef):
original_diagnostic = histogram.SparseDiagnostic.get_by_id(
diagnostic.guid)
diagnostic = diagnostic_module.Diagnostic.FromDict(
original_diagnostic.data)
# We don't have other diagnostics in
# histogram_helpers.ADD_HISTOGRAM_RELATED_DIAGNOSTICS if they apper
# in the future, dimensions format should be changed.
assert isinstance(diagnostic, generic_set.GenericSet)
info['dimensions'].append({'name': name, 'value': list(diagnostic)})
raise ndb.Return(info)
def _GenerateResponse(token,
get_measurement_info=False,
get_dimensions_info=False):
measurements = token.GetMeasurements() if get_measurement_info else []
result = {
'token': token.key.id(),
'file': token.temporary_staging_file_path,
'created': str(token.creation_time),
'lastUpdated': str(token.update_time),
'state': upload_completion_token.StateToString(token.state),
}
if token.error_message is not None:
result['error_message'] = token.error_message
if measurements:
result['measurements'] = []
measurement_info_futures = []
for measurement in measurements:
measurement_info_futures.append(
_GenerateMeasurementInfo(measurement, get_dimensions_info))
ndb.Future.wait_all(measurement_info_futures)
for future in measurement_info_futures:
result['measurements'].append(future.get_result())
return result
@api_request_handler.RequestHandlerDecoratorFactory(_CheckUser)
def UploadsInfoGet(token_id):
"""Returns json, that describes state of the token.
Can be called by get request to /uploads/<token_id>. Measurements info can
be requested with GET parameter ?additional_info=measurements. If dimensions
info is also required use ?additional_info=measurements,dimensions.
Response is json of the form:
{
"token": "...",
"file": "...",
"created": "...",
"lastUpdated": "...",
"state": "PENDING|PROCESSING|FAILED|COMPLETED",
"error_message": "...",
"measurements": [
{
"name": "...",
"state": "PROCESSING|FAILED|COMPLETED",
"monitored": True|False,
"lastUpdated": "...",
"error_message": "...",
"dimensions": [
{
"name": "...",
"values": ["...", ...]
},
...
]
},
...
]
}
Description of the fields:
- token: Token id from the request.
- file: Temporary staging file path, where /add_histogram request data is
stored during the PENDING stage. For more information look at
/add_histogram api.
- created: Date and time of creation.
- lastUpdated: Date and time of last update.
- state: Aggregated state of the token and all associated measurements.
- error_message: If historgam upload failed on token level (during
/add_histogram) will contain addition information about failure.
Absent otherwise.
- measurements: List of json objects, that describes measurements
associated with the token. If there are no such measurements, the field
will be absent. This field may be absent if full information is not
requested.
- name: The path of the measurement. It is a fully-qualified path in
the Dashboard.
- state: State of the measurement.
- error_message: If state is FAILED, contains addition information
about failure. Absent otherwise.
- monitored: A boolean indicating whether the path is monitored by a
sheriff configuration.
- lastUpdated: Date and time of last update.
- dimensions: List of relevant for /add_histogram api diagnostics,
associated to the histogram, that is represented by the measurement.
This field will be present in response only after the histogram has
been added to Datastore. This field may be absent if full information
is not requested.
- name: Name of the diagnostic.
- values: List of values, stored in the GenericSet diagnostic.
Meaning of some common error codes:
- 400: Invalid format of the token id.
- 403: The user is not authorized to check on the status of an upload.
- 404: Token could not be found. It is either expired or was never
created.
"""
if not _IsValidUuid(token_id):
logging.error('Upload completion token id is not valid. Token id: %s',
token_id)
raise ValueError
token = upload_completion_token.Token.get_by_id(token_id)
if token is None:
logging.error('Upload completion token not found. Token id: %s', token_id)
raise api_request_handler.NotFoundError
additional_info = request.values.get('additional_info')
if additional_info:
get_measurement_info = 'measurements' in additional_info
get_dimensions_info = 'dimensions' in additional_info
return _GenerateResponse(token, get_measurement_info, get_dimensions_info)
return _GenerateResponse(token)