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

Unified Diff: tracing/tracing/metrics/compare_samples_unittest.py

Issue 2089833002: Entry point for bisect sample comparison. (Closed) Base URL: https://github.com/catapult-project/catapult.git@mann
Patch Set: Closing low level file handle for temp file. Created 4 years, 2 months 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: tracing/tracing/metrics/compare_samples_unittest.py
diff --git a/tracing/tracing/metrics/compare_samples_unittest.py b/tracing/tracing/metrics/compare_samples_unittest.py
new file mode 100644
index 0000000000000000000000000000000000000000..730f7915d67b418526f393fd3e5dd815a629b20e
--- /dev/null
+++ b/tracing/tracing/metrics/compare_samples_unittest.py
@@ -0,0 +1,225 @@
+# Copyright 2016 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.
+
+import json
+import math
+import os
+import random
+import tempfile
+import unittest
+
+from tracing.metrics import compare_samples
+
+
+REJECT = 'REJECT'
+FAIL_TO_REJECT = 'FAIL_TO_REJECT'
+NEED_MORE_DATA = 'NEED_MORE_DATA'
+
+
+def Mean(l):
+ if len(l):
+ return float(sum(l))/len(l)
+ return 0
+
+
+class CompareSamplesUnittest(unittest.TestCase):
+ def setUp(self):
+ self._tempfiles = []
+ self._tempdir = tempfile.mkdtemp()
+
+ def tearDown(self):
+ for tf in self._tempfiles:
+ try:
+ os.remove(tf)
+ except OSError:
+ pass
+ try:
+ os.rmdir(self._tempdir)
+ except OSError:
+ pass
+
+ def NewJsonTempfile(self, jsonable_contents):
+ f_handle, new_json_file = tempfile.mkstemp(
+ suffix='.json',
+ dir=self._tempdir,
+ text=True)
+ os.close(f_handle)
+ self._tempfiles.append(new_json_file)
+ with open(new_json_file, 'w') as f:
+ json.dump(jsonable_contents, f)
+ return new_json_file
+
+ def MakeMultipleChartJSONHistograms(self, metric, seed, mu, sigma, n, m):
+ result = []
+ random.seed(seed)
+ for _ in range(m):
+ result.append(self.MakeChartJSONHistogram(metric, mu, sigma, n))
+ return result
+
+ def MakeChartJSONHistogram(self, metric, mu, sigma, n):
+ """Creates a histogram for a normally distributed pseudo-random sample.
+
+ This function creates a deterministic pseudo-random sample and stores it in
+ chartjson histogram format to facilitate the testing of the sample
+ comparison logic.
+
+ For simplicity we use sqrt(n) buckets with equal widths.
+
+ Args:
+ metric (str pair): name of chart, name of the trace.
+ seed (hashable obj): to make the sequences deterministic we seed the RNG.
+ mu (float): desired mean for the sample
+ sigma (float): desired standard deviation for the sample
+ n (int): number of values to generate.
+ """
+ chart_name, trace_name = metric
+ values = [random.gauss(mu, sigma) for _ in range(n)]
+ bucket_count = int(math.ceil(math.sqrt(len(values))))
+ width = (max(values) - min(values))/(bucket_count - 1)
+ prev_bucket = min(values)
+ buckets = []
+ for _ in range(bucket_count):
+ buckets.append({'low': prev_bucket,
+ 'high': prev_bucket + width,
+ 'count': 0})
+ prev_bucket += width
+ for value in values:
+ for bucket in buckets:
+ if value >= bucket['low'] and value < bucket['high']:
+ bucket['count'] += 1
+ break
+ charts = {
+ 'charts': {
+ chart_name: {
+ trace_name: {
+ 'type': 'histogram',
+ 'buckets': buckets
+ }
+ }
+ }
+ }
+ return self.NewJsonTempfile(charts)
+
+ def MakeChart(self, metric, seed, mu, sigma, n):
+ """Creates a normally distributed pseudo-random sample. (continuous).
+
+ This function creates a deterministic pseudo-random sample and stores it in
+ chartjson format to facilitate the testing of the sample comparison logic.
+
+ Args:
+ metric (str pair): name of chart, name of the trace.
+ seed (hashable obj): to make the sequences deterministic we seed the RNG.
+ mu (float): desired mean for the sample
+ sigma (float): desired standard deviation for the sample
+ n (int): number of values to generate.
+ """
+ chart_name, trace_name = metric
+ random.seed(seed)
+ values = [random.gauss(mu, sigma) for _ in range(n)]
+ charts = {
+ 'charts': {
+ chart_name: {
+ trace_name: {
+ 'type': 'list_of_scalar_values',
+ 'values': values}
+ }
+ }
+ }
+ return self.NewJsonTempfile(charts)
+
+ def testCompareClearRegression(self):
+ metric = ('some_chart', 'some_trace')
+ lower_values = ','.join([self.MakeChart(metric=metric, seed='lower',
+ mu=10, sigma=1, n=10)])
+ higher_values = ','.join([self.MakeChart(metric=metric, seed='higher',
+ mu=20, sigma=2, n=10)])
+ result = json.loads(compare_samples.CompareSamples(
+ lower_values, higher_values, '/'.join(metric)).stdout)
+ self.assertEqual(result['result']['significance'], REJECT)
+
+ def testCompareUnlikelyRegressionWithMultipleRuns(self):
+ metric = ('some_chart', 'some_trace')
+ lower_values = ','.join([
+ self.MakeChart(
+ metric=metric, seed='lower%d' % i, mu=10, sigma=1, n=5)
+ for i in range(4)])
+ higher_values = ','.join([
+ self.MakeChart(
+ metric=metric, seed='higher%d' % i, mu=10.01, sigma=0.95, n=5)
+ for i in range(4)])
+ result = json.loads(compare_samples.CompareSamples(
+ lower_values, higher_values, '/'.join(metric)).stdout)
+ self.assertEqual(result['result']['significance'], FAIL_TO_REJECT)
+
+ def testCompareInsufficientData(self):
+ metric = ('some_chart', 'some_trace')
+ lower_values = ','.join([self.MakeChart(metric=metric, seed='lower',
+ mu=10, sigma=1, n=5)])
+ higher_values = ','.join([self.MakeChart(metric=metric, seed='higher',
+ mu=10.40, sigma=0.95, n=5)])
+ result = json.loads(compare_samples.CompareSamples(
+ lower_values, higher_values, '/'.join(metric)).stdout)
+ self.assertEqual(result['result']['significance'], NEED_MORE_DATA)
+
+ def testCompareMissingFile(self):
+ metric = ('some_chart', 'some_trace')
+ lower_values = ','.join([self.MakeChart(metric=metric, seed='lower',
+ mu=10, sigma=1, n=5)])
+ higher_values = '/path/does/not/exist.json'
+ with self.assertRaises(RuntimeError):
+ compare_samples.CompareSamples(
+ lower_values, higher_values, '/'.join(metric))
+
+ def testCompareMissingMetric(self):
+ metric = ('some_chart', 'some_trace')
+ lower_values = ','.join([self.MakeChart(metric=metric, seed='lower',
+ mu=10, sigma=1, n=5)])
+ higher_values = ','.join([self.MakeChart(metric=metric, seed='higher',
+ mu=20, sigma=2, n=5)])
+ metric = ('some_chart', 'missing_trace')
+ with self.assertRaises(RuntimeError):
+ compare_samples.CompareSamples(
+ lower_values, higher_values, '/'.join(metric))
+
+ def testCompareBadChart(self):
+ metric = ('some_chart', 'some_trace')
+ lower_values = ','.join([self.MakeChart(metric=metric, seed='lower',
+ mu=10, sigma=1, n=5)])
+ higher_values = self.NewJsonTempfile(['obviously', 'not', 'a', 'chart]'])
+ with self.assertRaises(RuntimeError):
+ compare_samples.CompareSamples(
+ lower_values, higher_values, '/'.join(metric))
+
+ def testCompareValuesets(self):
+ vs = os.path.join(os.path.dirname(__file__),
+ 'valueset_output_for_compare_samples_test.json')
+ result = compare_samples.CompareSamples(
+ vs, vs, 'timeToFirstContentfulPaint/pcv1-cold/'
+ 'http___www.rambler.ru_', data_format='valueset')
+ result = json.loads(result.stdout)
+ self.assertEqual(result['result']['significance'], NEED_MORE_DATA)
+ self.assertAlmostEqual(Mean(result['sampleA']), 75.3177999958396)
+ self.assertAlmostEqual(Mean(result['sampleB']), 75.3177999958396)
+
+ def testCompareBuildbotOutput(self):
+ bb = os.path.join(os.path.dirname(__file__),
+ 'buildbot_output_for_compare_samples_test.txt')
+ result = compare_samples.CompareSamples(
+ bb, bb, 'DrawCallPerf_gl/score',
+ data_format='buildbot')
+ result = json.loads(result.stdout)
+ self.assertEqual(result['result']['significance'], NEED_MORE_DATA)
+ self.assertEqual(Mean(result['sampleA']), 4123)
+ self.assertEqual(Mean(result['sampleB']), 4123)
+
+ def testCompareChartJsonHistogram(self):
+ metric = ('some_chart', 'some_trace')
+ lower_values = ','.join(self.MakeMultipleChartJSONHistograms(
+ metric=metric, seed='lower', mu=10, sigma=1, n=100, m=10))
+ higher_values = ','.join(self.MakeMultipleChartJSONHistograms(
+ metric=metric, seed='higher', mu=20, sigma=2, n=100, m=10))
+ result = json.loads(compare_samples.CompareSamples(
+ lower_values, higher_values, '/'.join(metric)).stdout)
+ self.assertEqual(result['result']['significance'], REJECT)
+

Powered by Google App Engine
This is Rietveld 408576698