Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(794)

Unified Diff: appengine_apps/trooper_o_matic/appengine_module/trooper_o_matic/cron.py

Issue 774323002: Moved trooper_o_matic to appengine/ (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: appengine_apps/trooper_o_matic/appengine_module/trooper_o_matic/cron.py
diff --git a/appengine_apps/trooper_o_matic/appengine_module/trooper_o_matic/cron.py b/appengine_apps/trooper_o_matic/appengine_module/trooper_o_matic/cron.py
deleted file mode 100644
index 9baaf5c910a3208647b81b1d0c4115a280d6dfe2..0000000000000000000000000000000000000000
--- a/appengine_apps/trooper_o_matic/appengine_module/trooper_o_matic/cron.py
+++ /dev/null
@@ -1,290 +0,0 @@
-# Copyright (c) 2014 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.
-
-"""Collect stats regularly via app engine cron.
-"""
-
-import calendar
-import datetime
-import json
-import logging
-
-import numpy
-import webapp2
-
-from google.appengine.api import urlfetch
-from google.appengine.ext import ndb
-
-from appengine_module.trooper_o_matic import models
-from appengine_module.trooper_o_matic import trees
-
-
-def datetime_now(): # pragma: no cover
- """Easy to mock datetime.datetime.utcnow() for unit testing."""
- return datetime.datetime.utcnow()
-
-
-def date_from_str(string, base_format): # pragma: no cover
- """Converts a string to a date, taking into account the possible existence
- of a millisecond precision value."""
- try:
- return datetime.datetime.strptime(string, base_format + '.%f')
- except ValueError:
- return datetime.datetime.strptime(string, base_format)
-
-
-class CheckCQHandler(webapp2.RequestHandler): # pragma: no cover
- """Collect commit queue length and run times."""
-
- patch_stop_list = ('http://chromium-cq-status.appspot.com/query/action='
- 'patch_stop/?begin=%d')
-
- pending_api_url = 'https://chromium-commit-queue.appspot.com/api/%s/pending'
-
- patchset_details = ('https://chromium-cq-status.appspot.com/query/'
- 'issue=%d/patchset=%d/')
-
- @staticmethod
- def update_stat_for_times(stat, times):
- stat.min = times[0]
- stat.max = times[-1]
- stat.mean = numpy.mean(times)
- stat.p10 = numpy.percentile(times, 10)
- stat.p25 = numpy.percentile(times, 25)
- stat.p50 = numpy.percentile(times, 50)
- stat.p75 = numpy.percentile(times, 75)
- stat.p90 = numpy.percentile(times, 90)
- stat.p95 = numpy.percentile(times, 95)
- stat.p99 = numpy.percentile(times, 99)
-
- def get(self):
- # We only care about the last hour.
- cutoff = datetime_now() - datetime.timedelta(hours=1)
- url = self.patch_stop_list % calendar.timegm(
- cutoff.timetuple())
-
- # CQ API has a limit of results it will return, and if there are more
- # results it will return a cursor. So loop through results until
- # there is no cursor.
- cursor = None
- more_results = True
- patchsets = {}
- while more_results:
- if cursor:
- url = url + '&cursor=' + cursor
- result = urlfetch.fetch(url=url, deadline=60)
- content = json.loads(result.content)
- for result in content['results']:
- patchsets.setdefault(result['fields']['project'], set()).add(
- (result['fields']['issue'], result['fields']['patchset']))
- cursor = content.get('cursor')
- more_results = content.get('more')
-
- # Only track the chromium and blink projects.
- projects = set(['chromium', 'blink'])
- for project in projects:
- # Ensure there is an ancestor for all the stats for this project.
- project_model = models.Project.get_or_insert(project)
- project_model.put()
-
- # CQ exposes an API for its length.
- result = urlfetch.fetch(url=self.pending_api_url % project, deadline=60)
- pending = set(json.loads(result.content)['results'])
- num_pending = len(pending)
- stat = models.CqStat(parent=project_model.key, length=num_pending)
- patch_in_queue_stat = models.CqTimeInQueueForPatchStat(
- parent=project_model.key, length=num_pending)
- patch_total_time_stat = models.CqTotalTimeForPatchStat(
- parent=project_model.key, length=num_pending)
-
- single_run_times = []
- in_queue_times = []
- total_times = []
-
- for patchset in patchsets[project]:
- url = self.patchset_details % (patchset[0], patchset[1])
- result = urlfetch.fetch(url=url, deadline=60)
- content = json.loads(result.content)
- # Get a list of all starts/stops for this patch.
- actions = [result['fields'] for result in content['results'] if (
- result['fields'].get('action') == 'patch_start' or
- result['fields'].get('action') == 'patch_stop')]
- actions.sort(key=lambda k: k['timestamp'])
-
- start_time = None
- last_start = None
- end_time = None
- run_times = []
- for action in actions:
- if action['action'] == 'patch_start':
- if not start_time:
- start_time = action['timestamp']
- last_start = action['timestamp']
- else:
- if last_start:
- run_time = (action['timestamp'] - last_start) / 60
- run_times.append(run_time)
- last_start = None
- end_time = action['timestamp']
-
- if run_times:
- single_run_times += run_times
- in_queue_times.append(sum(run_times))
- total_times.append((end_time - start_time) / 60)
-
- if single_run_times:
- self.update_stat_for_times(stat, sorted(single_run_times))
- self.update_stat_for_times(patch_in_queue_stat, sorted(in_queue_times))
- self.update_stat_for_times(patch_total_time_stat, sorted(total_times))
-
- stat.put()
- patch_in_queue_stat.put()
- patch_total_time_stat.put()
-
-
-class CheckTreeHandler(webapp2.RequestHandler): # pragma: no cover
- """Checks the given tree for build times higher than the SLO specifies."""
-
- stats_api_url = ('https://chrome-infra-stats.appspot.com/_ah/api/stats/v1/'
- 'steps/%s/overall__build__result__/%s')
-
- last_hour_format = '%Y-%m-%dT%H:%MZ'
- generated_format = '%Y-%m-%dT%H:%M:%S'
-
- def get(self, tree):
- """For each master in the tree, find builds that don't meet our SLO."""
- masters = trees.GetMastersForTree(tree)
- if not masters:
- logging.error('Invalid tree %s', tree)
- return
- now = datetime_now()
- tree_model = models.Tree.get_or_insert(tree)
- tree_model.put()
- stat = models.BuildTimeStat(parent=tree_model.key,
- timestamp=now,
- num_builds=0,
- num_over_median_slo=0,
- num_over_max_slo=0)
- # The chrome-infra-stats API lists builds that have STARTED in the last
- # hour. We want to list builds that have ENDED in the last hour, so we need
- # to go back through the last 24 hours to make sure we don't miss any.
- # TODO(sullivan): When an "ended in last hour" API is available, switch
- # to that.
- hours = [now - datetime.timedelta(hours=h) for h in range(0, 24)]
- hour_strs = [hour.strftime(self.last_hour_format) for hour in hours]
- last_hour = datetime.timedelta(hours=1)
- for master in masters:
- records = []
- urls = [self.stats_api_url % (master, hour_str) for hour_str in hour_strs]
- for url in urls:
- logging.info(url)
- result = urlfetch.fetch(url=url, deadline=60)
- content = json.loads(result.content)
- records += content.get('step_records', [])
- for record in records:
- generated_time = date_from_str(record['generated'],
- self.generated_format)
- if now - generated_time > last_hour:
- continue
- stat.num_builds += 1
- buildtime_median = models.SLO_BUILDTIME_PER_BOT_MEDIAN.get(
- master, {}).get(record['builder'], models.SLO_BUILDTIME_MEDIAN)
- buildtime_max = models.SLO_BUILDTIME_PER_BOT_MAX.get(
- master, {}).get(record['builder'], models.SLO_BUILDTIME_MAX)
- buildtime_max = max(buildtime_max, buildtime_median)
-
- if record['step_time'] > buildtime_median:
- stat.num_over_median_slo += 1
- v = models.BuildSLOOffender(tree=tree, master=master,
- builder=record['builder'],
- buildnumber=int(record['buildnumber']),
- buildtime=float(record['step_time']),
- result=int(record['result']),
- revision=record['revision'],
- slo_median_buildtime=buildtime_median,
- slo_max_buildtime=buildtime_max)
- stat.slo_offenders.append(v)
- if record['step_time'] > buildtime_max:
- stat.num_over_max_slo += 1
- ndb.put_multi(stat.slo_offenders)
- stat.put()
-
-
-class CheckTreeStatusHandler(webapp2.RequestHandler): # pragma: no cover
-
- status_url = ('https://%s-status.appspot.com/allstatus?format=json&'
- 'endTime=%s&limit=1000')
-
- @staticmethod
- def tree_is_open_for(entry):
- # Count scheduled maintenance as tree open, we only want to alert on
- # unexpected closures.
- return (entry['can_commit_freely'] or
- entry['message'].startswith('Tree is closed for maintenance'))
-
- @staticmethod
- def date_for( entry):
- return datetime.datetime.strptime(entry['date'], '%Y-%m-%d %H:%M:%S.%f')
-
- def fetch_entries(self, project, days):
- # Get two previous days of data, in case the tree has been in the same
- # state for the entire time period.
- data_start = datetime_now() - datetime.timedelta(days=days+2)
- url = self.status_url % (project, calendar.timegm(data_start.timetuple()))
- result = urlfetch.fetch(url)
- entries = json.loads(result.content)
- entries.sort(key=self.date_for)
- return entries
-
- def get_state_of_tree(self, entries, cutoff):
- # Find the state of the tree before the days started.
- was_open = True
- for _, entry in enumerate(entries):
- if self.date_for(entry) > cutoff:
- break
- was_open = self.tree_is_open_for(entry)
- return was_open
-
- def get(self, project, days):
- # Check tree status in last N days
- days = int(days)
- now = datetime_now()
- cutoff = datetime_now() - datetime.timedelta(days=days)
-
- entries = self.fetch_entries(project, days)
- was_open = self.get_state_of_tree(entries, cutoff)
-
- # Now look through the entries in the relevant days to find the tree open
- # times.
- last_change = cutoff
- open_time = datetime.timedelta(seconds=0)
- closed_time = datetime.timedelta(seconds=0)
- for entry in entries:
- is_open = self.tree_is_open_for(entry)
- if self.date_for(entry) <= cutoff or is_open == was_open:
- continue
- current_time = self.date_for(entry)
- delta = current_time - last_change
- if was_open:
- open_time += delta
- else:
- closed_time += delta
- last_change = current_time
- was_open = is_open
-
- delta = now - last_change
- if was_open:
- open_time += delta
- else:
- closed_time += delta
-
- open_seconds = open_time.total_seconds()
- closed_seconds = closed_time.total_seconds()
- project_model = models.Project.get_or_insert(project)
- project_model.put()
- stat = models.TreeOpenStat(
- parent=project_model.key,
- num_days=days,
- percent_open=(open_seconds / (open_seconds + closed_seconds)) * 100)
- stat.put()

Powered by Google App Engine
This is Rietveld 408576698