blob: 61f34ff95dae07b2192e2016b3cf503add65b442 [file] [log] [blame]
# Copyright 2018 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.
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
import logging
import os
import six
from google.appengine.ext import deferred
from google.appengine.ext import ndb
from dashboard import list_tests
from dashboard import update_test_suites
from dashboard.common import datastore_hooks
from dashboard.common import namespaced_stored_object
from dashboard.common import stored_object
from dashboard.common import utils
from dashboard.models import graph_data
from dashboard.models import histogram
from tracing.value.diagnostics import reserved_infos
from tracing.value.diagnostics import generic_set
from flask import request, make_response
def CacheKey(master, test_suite):
return 'test_suite_descriptor_%s_%s' % (master, test_suite)
def FetchCachedTestSuiteDescriptor(master, test_suite):
masters = [master]
if not master:
masters = _GetMastersForSuite(test_suite)
futures = [
namespaced_stored_object.GetAsync(CacheKey(m, test_suite))
for m in masters
]
ndb.Future.wait_all(futures)
desc = {'measurements': [], 'bots': [], 'cases': [], 'caseTags': {}}
for f in futures:
cur_desc = f.get_result()
if cur_desc is not None:
desc['measurements'].extend(cur_desc['measurements'])
desc['bots'].extend(cur_desc['bots'])
desc['cases'].extend(cur_desc['cases'])
for tag, case_tags in six.iteritems(cur_desc['caseTags']):
desc['caseTags'].setdefault(tag, []).extend(case_tags)
desc['measurements'] = list(sorted(set(desc['measurements'])))
desc['bots'] = list(sorted(set(desc['bots'])))
desc['cases'] = list(sorted(set(desc['cases'])))
for tag in desc['caseTags']:
desc['caseTags'][tag] = list(sorted(set(desc['caseTags'][tag])))
return desc
def UpdateTestSuiteDescriptorsPost():
namespace = datastore_hooks.EXTERNAL
if request.values.get('internal_only') == 'true':
namespace = datastore_hooks.INTERNAL
UpdateTestSuiteDescriptors(namespace)
return make_response('')
def UpdateTestSuiteDescriptors(namespace):
key = namespaced_stored_object.NamespaceKey(
update_test_suites.TEST_SUITES_2_CACHE_KEY, namespace)
for test_suite in stored_object.Get(key):
ScheduleUpdateDescriptor(test_suite, namespace)
def ScheduleUpdateDescriptor(test_suite, namespace):
deferred.defer(_UpdateDescriptorByMaster, test_suite, namespace)
@ndb.tasklet
def _QueryCaseTags(test_path, case):
data_by_name = yield histogram.SparseDiagnostic.GetMostRecentDataByNamesAsync(
utils.TestKey(test_path), [reserved_infos.STORY_TAGS.name])
data = data_by_name.get(reserved_infos.STORY_TAGS.name)
tags = list(generic_set.GenericSet.FromDict(data)) if data else []
raise ndb.Return((case, tags))
def _CollectCaseTags(futures, case_tags):
ndb.Future.wait_all(futures)
for future in futures:
case, tags = future.get_result()
for tag in tags:
case_tags.setdefault(tag, []).append(case)
TESTS_TO_FETCH = 5000
def _GetMastersForSuite(suite):
masters = list_tests.GetTestsMatchingPattern('*/*/%s' % suite)
masters = list({m.split('/')[0] for m in masters})
return masters
def _UpdateDescriptorByMaster(test_suite, namespace):
masters = _GetMastersForSuite(test_suite)
for m in masters:
deferred.defer(_UpdateDescriptor, m, test_suite, namespace)
def _UpdateDescriptor(master,
test_suite,
namespace,
start_cursor=None,
measurements=(),
bots=(),
cases=(),
case_tags=None):
logging.info('%s %s %s %d %d %d', master, test_suite, namespace,
len(measurements), len(bots), len(cases))
# This function always runs in the taskqueue as an anonymous user.
if namespace == datastore_hooks.INTERNAL:
try:
datastore_hooks.SetPrivilegedRequest()
except RuntimeError:
# _UpdateDescriptor is called from deferred queue, and the value of
# PATH_INFO, '/_ah/queue/deferred', will qualify the request as
# privileged. We should be safe to skip the SetPrivilegedRequest here.
path_info = os.environ.get('PATH_INFO', None)
if path_info:
logging.info(
'No flask context found. Privileged request check will rely on PATH_INFO: %s',
path_info)
else:
logging.error(
'Failed to set privileged request for internal descriptor update.')
return
measurements = set(measurements)
bots = set(bots)
cases = set(cases)
case_tags = case_tags or {}
tags_futures = []
query = graph_data.TestMetadata.query()
query = query.filter(graph_data.TestMetadata.master_name == master)
query = query.filter(graph_data.TestMetadata.suite_name == test_suite)
query = query.filter(graph_data.TestMetadata.deprecated == False)
query = query.filter(graph_data.TestMetadata.has_rows == True)
tests, next_cursor, more = query.fetch_page(
TESTS_TO_FETCH,
start_cursor=start_cursor,
use_cache=False,
use_memcache=False)
for test in tests:
bots.add(test.bot_name)
try:
_, measurement, story = utils.ParseTelemetryMetricParts(test.test_path)
except utils.ParseTelemetryMetricFailed as e:
# Log the error and process the rest of the test suite.
logging.error('Parsing error encounted: %s', e)
continue
if test.unescaped_story_name:
story = test.unescaped_story_name
if measurement:
measurements.add(measurement)
if story and story not in cases:
cases.add(story)
tags_futures.append(_QueryCaseTags(test.test_path, story))
_CollectCaseTags(tags_futures, case_tags)
logging.info('%d keys, %d measurements, %d bots, %d cases, %d tags',
len(tests), len(measurements), len(bots), len(cases),
len(case_tags))
if more:
logging.info('continuing')
deferred.defer(_UpdateDescriptor, master, test_suite, namespace,
next_cursor, measurements, bots, cases, case_tags)
return
desc = {
'measurements': list(sorted(measurements)),
'bots': list(sorted(bots)),
'cases': list(sorted(cases)),
'caseTags': {
tag: sorted(cases) for tag, cases in list(case_tags.items())
}
}
key = namespaced_stored_object.NamespaceKey(
CacheKey(master, test_suite), namespace)
stored_object.Set(key, desc)