Skip to content

Commit

Permalink
Import metrics_center (flutter#268)
Browse files Browse the repository at this point in the history
  • Loading branch information
dnfield authored Jan 23, 2021
1 parent 9b3839c commit ca4992e
Show file tree
Hide file tree
Showing 25 changed files with 1,932 additions and 1 deletion.
2 changes: 1 addition & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ linter:
- always_specify_types
- annotate_overrides
# - avoid_annotating_with_dynamic # conflicts with always_specify_types
- avoid_as
# - avoid_as # conflicts with NNBD
- avoid_bool_literals_in_conditional_expressions
# - avoid_catches_without_on_clauses # we do this commonly
# - avoid_catching_errors # we do this commonly
Expand Down
1 change: 1 addition & 0 deletions packages/metrics_center/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
secret
3 changes: 3 additions & 0 deletions packages/metrics_center/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# 0.0.4+1

- Moved to the `flutter/packages` repository
25 changes: 25 additions & 0 deletions packages/metrics_center/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Copyright 2014 The Flutter Authors. All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
9 changes: 9 additions & 0 deletions packages/metrics_center/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Metrics Center

Metrics center is a minimal set of code and services to support multiple perf
metrics generators (e.g., Cocoon device lab, Cirrus bots, LUCI bots, Firebase
Test Lab) and destinations (e.g., old Cocoon perf dashboard, Skia perf
dashboard). The work and maintenance it requires is very close to that of just
supporting a single generator and destination (e.g., engine bots to Skia perf),
and the small amount of extra work is designed to make it easy to support more
generators and destinations in the future.
8 changes: 8 additions & 0 deletions packages/metrics_center/lib/metrics_center.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

export 'src/common.dart';
export 'src/flutter.dart';
export 'src/google_benchmark.dart';
export 'src/skiaperf.dart';
65 changes: 65 additions & 0 deletions packages/metrics_center/lib/src/common.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright 2014 The Flutter 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 'dart:collection';
import 'dart:convert';

import 'package:crypto/crypto.dart';
import 'package:equatable/equatable.dart';

import 'package:googleapis_auth/auth.dart';
import 'package:googleapis_auth/auth_io.dart';
import 'package:http/http.dart';

/// Common format of a metric data point.
class MetricPoint extends Equatable {
/// Creates a new data point.
MetricPoint(
this.value,
Map<String, String> tags,
) : _tags = SplayTreeMap<String, String>.from(tags);

/// Can store integer values.
final double value;

/// Test name, unit, timestamp, configs, git revision, ..., in sorted order.
UnmodifiableMapView<String, String> get tags =>
UnmodifiableMapView<String, String>(_tags);

/// Unique identifier for updating existing data point.
///
/// We shouldn't have to worry about hash collisions until we have about
/// 2^128 points.
///
/// This id should stay constant even if the [tags.keys] are reordered.
/// (Because we are using an ordered SplayTreeMap to generate the id.)
String get id => sha256.convert(utf8.encode('$_tags')).toString();

@override
String toString() {
return 'MetricPoint(value=$value, tags=$_tags)';
}

final SplayTreeMap<String, String> _tags;

@override
List<Object> get props => <Object>[value, tags];
}

/// Interface to write [MetricPoint].
abstract class MetricDestination {
/// Insert new data points or modify old ones with matching id.
Future<void> update(List<MetricPoint> points);
}

/// Create `AuthClient` in case we only have an access token without the full
/// credentials json. It's currently the case for Chrmoium LUCI bots.
AuthClient authClientFromAccessToken(String token, List<String> scopes) {
final DateTime anHourLater = DateTime.now().add(const Duration(hours: 1));
final AccessToken accessToken =
AccessToken('Bearer', token, anHourLater.toUtc());
final AccessCredentials accessCredentials =
AccessCredentials(accessToken, null, scopes);
return authenticatedClient(Client(), accessCredentials);
}
55 changes: 55 additions & 0 deletions packages/metrics_center/lib/src/constants.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/// Strings used for MetricPoint tag keys
const String kGithubRepoKey = 'gitRepo';

/// Strings used for MetricPoint tag keys
const String kGitRevisionKey = 'gitRevision';

/// Strings used for MetricPoint tag keys
const String kUnitKey = 'unit';

/// Strings used for MetricPoint tag keys
const String kNameKey = 'name';

/// Strings used for MetricPoint tag keys
const String kSubResultKey = 'subResult';

/// Flutter repo name.
const String kFlutterFrameworkRepo = 'flutter/flutter';

/// Engine repo name.
const String kFlutterEngineRepo = 'flutter/engine';

/// The key for the GCP project id in the credentials json.
const String kProjectId = 'project_id';

/// Timeline key in JSON.
const String kSourceTimeMicrosName = 'sourceTimeMicros';

/// The size of 500 is currently limited by Google datastore. It cannot write
/// more than 500 entities in a single call.
const int kMaxBatchSize = 500;

/// The prod bucket name for Flutter's instance of Skia Perf.
const String kBucketName = 'flutter-skia-perf-prod';

/// The test bucket name for Flutter's instance of Skia Perf.
const String kTestBucketName = 'flutter-skia-perf-test';

/// JSON key for Skia Perf's git hash entry.
const String kSkiaPerfGitHashKey = 'gitHash';

/// JSON key for Skia Perf's results entry.
const String kSkiaPerfResultsKey = 'results';

/// JSON key for Skia Perf's value entry.
const String kSkiaPerfValueKey = 'value';

/// JSON key for Skia Perf's options entry.
const String kSkiaPerfOptionsKey = 'options';

/// JSON key for Skia Perf's default config entry.
const String kSkiaPerfDefaultConfig = 'default';
60 changes: 60 additions & 0 deletions packages/metrics_center/lib/src/flutter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright 2014 The Flutter 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 'common.dart';
import 'constants.dart';
import 'legacy_datastore.dart';
import 'legacy_flutter.dart';

/// Convenient class to capture the benchmarks in the Flutter engine repo.
class FlutterEngineMetricPoint extends MetricPoint {
/// Creates a metric point for the Flutter engine repository.
///
/// The `name`, `value`, and `gitRevision` parameters must not be null.
FlutterEngineMetricPoint(
String name,
double value,
String gitRevision, {
Map<String, String> moreTags = const <String, String>{},
}) : super(
value,
<String, String>{
kNameKey: name,
kGithubRepoKey: kFlutterEngineRepo,
kGitRevisionKey: gitRevision,
}..addAll(moreTags),
);
}

/// All Flutter performance metrics (framework, engine, ...) should be written
/// to this destination.
class FlutterDestination extends MetricDestination {
// TODO(liyuqian): change the implementation of this class (without changing
// its public APIs) to remove `LegacyFlutterDestination` and directly use
// `SkiaPerfDestination` once the migration is fully done.
FlutterDestination._(this._legacyDestination);

/// Creates a [FlutterDestination] from service account JSON.
static Future<FlutterDestination> makeFromCredentialsJson(
Map<String, dynamic> json) async {
final LegacyFlutterDestination legacyDestination =
LegacyFlutterDestination(await datastoreFromCredentialsJson(json));
return FlutterDestination._(legacyDestination);
}

/// Creates a [FlutterDestination] from an OAuth access token.
static FlutterDestination makeFromAccessToken(
String accessToken, String projectId) {
final LegacyFlutterDestination legacyDestination = LegacyFlutterDestination(
datastoreFromAccessToken(accessToken, projectId));
return FlutterDestination._(legacyDestination);
}

@override
Future<void> update(List<MetricPoint> points) async {
await _legacyDestination.update(points);
}

final LegacyFlutterDestination _legacyDestination;
}
90 changes: 90 additions & 0 deletions packages/metrics_center/lib/src/gcs_lock.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright 2014 The Flutter 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 'package:googleapis/storage/v1.dart';
import 'package:googleapis_auth/auth.dart';

/// Global (in terms of earth) mutex using Google Cloud Storage.
class GcsLock {
/// Create a lock with an authenticated client and a GCS bucket name.
///
/// The client is used to communicate with Google Cloud Storage APIs.
GcsLock(this._client, this._bucketName)
: assert(_client != null),
assert(_bucketName != null) {
_api = StorageApi(_client);
}

/// Create a temporary lock file in GCS, and use it as a mutex mechanism to
/// run a piece of code exclusively.
///
/// There must be no existing lock file with the same name in order to
/// proceed. If multiple [GcsLock]s with the same `bucketName` and
/// `lockFileName` try [protectedRun] simultaneously, only one will proceed
/// and create the lock file. All others will be blocked.
///
/// When [protectedRun] finishes, the lock file is deleted, and other blocked
/// [protectedRun] may proceed.
///
/// If the lock file is stuck (e.g., `_unlock` is interrupted unexpectedly),
/// one may need to manually delete the lock file from GCS to unblock any
/// [protectedRun] that may depend on it.
Future<void> protectedRun(String lockFileName, Future<void> f()) async {
await _lock(lockFileName);
try {
await f();
} catch (e, stacktrace) {
print(stacktrace);
rethrow;
} finally {
await _unlock(lockFileName);
}
}

Future<void> _lock(String lockFileName) async {
final Object object = Object();
object.bucket = _bucketName;
object.name = lockFileName;
final Media content = Media(const Stream<List<int>>.empty(), 0);

Duration waitPeriod = const Duration(milliseconds: 10);
bool locked = false;
while (!locked) {
try {
await _api.objects.insert(object, _bucketName,
ifGenerationMatch: '0', uploadMedia: content);
locked = true;
} on DetailedApiRequestError catch (e) {
if (e.status == 412) {
// Status 412 means that the lock file already exists. Wait until
// that lock file is deleted.
await Future<void>.delayed(waitPeriod);
waitPeriod *= 2;
if (waitPeriod >= _kWarningThreshold) {
print(
'The lock is waiting for a long time: $waitPeriod. '
'If the lock file $lockFileName in bucket $_bucketName '
'seems to be stuck (i.e., it was created a long time ago and '
'no one seems to be owning it currently), delete it manually '
'to unblock this.',
);
}
} else {
rethrow;
}
}
}
}

Future<void> _unlock(String lockFileName) async {
await _api.objects.delete(_bucketName, lockFileName);
}

StorageApi _api;

final String _bucketName;
final AuthClient _client;

static const Duration _kWarningThreshold = Duration(seconds: 10);
}
35 changes: 35 additions & 0 deletions packages/metrics_center/lib/src/github_helper.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2014 The Flutter 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 'package:github/github.dart';

/// Singleton class to query some Github info with an in-memory cache.
class GithubHelper {
/// Return the singleton helper.
factory GithubHelper() {
return _singleton;
}

GithubHelper._internal();

/// The result is cached in memory so querying the same thing again in the
/// same process is fast.
///
/// Our unit test requires that calling this method 1000 times for the same
/// `githubRepo` and `sha` should be done in 1 second.
Future<DateTime> getCommitDateTime(String githubRepo, String sha) async {
final String key = '$githubRepo/commit/$sha';
if (_commitDateTimeCache[key] == null) {
final RepositoryCommit commit = await _github.repositories
.getCommit(RepositorySlug.full(githubRepo), sha);
_commitDateTimeCache[key] = commit.commit.committer.date;
}
return _commitDateTimeCache[key];
}

static final GithubHelper _singleton = GithubHelper._internal();

final GitHub _github = GitHub(auth: findAuthenticationFromEnvironment());
final Map<String, DateTime> _commitDateTimeCache = <String, DateTime>{};
}
Loading

0 comments on commit ca4992e

Please sign in to comment.