Skip to content

Commit d355778

Browse files
devoncarewcommit-bot@chromium.org
authored andcommitted
[analyzer] rate limit the crash reports we send
Change-Id: I9f6af7fbcec653a2a9396d1098bea678777d4b3b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/124761 Reviewed-by: Mike Fairhurst <mfairhurst@google.com> Commit-Queue: Devon Carew <devoncarew@google.com>
1 parent 0b210e5 commit d355778

File tree

5 files changed

+102
-12
lines changed

5 files changed

+102
-12
lines changed

pkg/analysis_server/lib/src/server/crash_reporting.dart

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,19 @@ class CrashReportingInstrumentation extends NoopInstrumentationService {
1313

1414
@override
1515
void logException(dynamic exception, [StackTrace stackTrace]) {
16-
reporter.sendReport(exception,
17-
stackTrace: stackTrace ?? StackTrace.current);
16+
reporter
17+
.sendReport(exception, stackTrace: stackTrace ?? StackTrace.current)
18+
.catchError((error) {
19+
// We silently ignore errors sending crash reports (network issues, ...).
20+
});
1821
}
1922

2023
@override
2124
void logPluginException(
2225
PluginData plugin, dynamic exception, StackTrace stackTrace) {
2326
// TODO(mfairhurst): send plugin information too.
24-
reporter.sendReport(exception, stackTrace: stackTrace);
27+
reporter.sendReport(exception, stackTrace: stackTrace).catchError((error) {
28+
// We silently ignore errors sending crash reports (network issues, ...).
29+
});
2530
}
2631
}

pkg/telemetry/lib/crash_reporting.dart

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ import 'dart:io';
88
import 'package:http/http.dart' as http;
99
import 'package:stack_trace/stack_trace.dart';
1010

11-
// Reporting disabled until we're set up on the backend.
12-
const bool CRASH_REPORTING_DISABLED = true;
11+
import 'src/utils.dart';
1312

1413
/// Tells crash backend that this is a Dart error (as opposed to, say, Java).
1514
const String _dartTypeId = 'DartError';
@@ -37,21 +36,37 @@ class CrashReportSender {
3736
static final Uri _baseUri = new Uri(
3837
scheme: 'https', host: _crashServerHost, path: _crashEndpointPath);
3938

39+
static const int _maxReportsToSend = 1000;
40+
4041
final String crashProductId;
4142
final EnablementCallback shouldSend;
4243
final http.Client _httpClient;
4344

44-
/// Create a new [CrashReportSender], using the data from the given
45-
/// [Analytics] instance.
46-
CrashReportSender(this.crashProductId, this.shouldSend,
47-
{http.Client httpClient})
48-
: _httpClient = httpClient ?? new http.Client();
45+
final ThrottlingBucket _throttle = ThrottlingBucket(10, Duration(minutes: 1));
46+
int _reportsSend = 0;
47+
48+
/// Create a new [CrashReportSender].
49+
CrashReportSender(
50+
this.crashProductId,
51+
this.shouldSend, {
52+
http.Client httpClient,
53+
}) : _httpClient = httpClient ?? new http.Client();
4954

5055
/// Sends one crash report.
5156
///
5257
/// The report is populated from data in [error] and [stackTrace].
5358
Future sendReport(dynamic error, {StackTrace stackTrace}) async {
54-
if (!shouldSend() || CRASH_REPORTING_DISABLED) {
59+
if (!shouldSend()) {
60+
return;
61+
}
62+
63+
// Check if we've sent too many reports recently.
64+
if (!_throttle.removeDrop()) {
65+
return;
66+
}
67+
68+
// Don't send too many total reports to crash reporting.
69+
if (_reportsSend >= _maxReportsToSend) {
5570
return;
5671
}
5772

pkg/telemetry/lib/src/utils.dart

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:math' as math;
6+
7+
/// A throttling algorithm. This models the throttling after a bucket with
8+
/// water dripping into it at the rate of 1 drop per replenish duration. If the
9+
/// bucket has water when an operation is requested, 1 drop of water is removed
10+
/// and the operation is performed. If not the operation is skipped. This
11+
/// algorithm lets operations be performed in bursts without throttling, but
12+
/// holds the overall average rate of operations to 1 per replenish duration.
13+
class ThrottlingBucket {
14+
final int bucketSize;
15+
final Duration replenishDuration;
16+
17+
int _drops;
18+
int _lastReplenish;
19+
20+
ThrottlingBucket(this.bucketSize, this.replenishDuration) {
21+
_drops = bucketSize;
22+
_lastReplenish = new DateTime.now().millisecondsSinceEpoch;
23+
}
24+
25+
bool removeDrop() {
26+
_checkReplenish();
27+
28+
if (_drops <= 0) {
29+
return false;
30+
} else {
31+
_drops--;
32+
return true;
33+
}
34+
}
35+
36+
void _checkReplenish() {
37+
int now = new DateTime.now().millisecondsSinceEpoch;
38+
39+
int replenishMillis = replenishDuration.inMilliseconds;
40+
41+
if (_lastReplenish + replenishMillis >= now) {
42+
int inc = (now - _lastReplenish) ~/ replenishMillis;
43+
_drops = math.min(_drops + inc, bucketSize);
44+
_lastReplenish += (replenishMillis * inc);
45+
}
46+
}
47+
}

pkg/telemetry/test/crash_reporting_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,6 @@ void main() {
3838
String body = utf8.decode(request.bodyBytes);
3939
expect(body, contains('String')); // error.runtimeType
4040
expect(body, contains('test-error'));
41-
}, skip: CRASH_REPORTING_DISABLED);
41+
});
4242
});
4343
}

pkg/telemetry/test/utils_test.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:telemetry/src/utils.dart';
6+
import 'package:test/test.dart';
7+
8+
void main() {
9+
group('ThrottlingBucket', () {
10+
test('can send', () {
11+
ThrottlingBucket bucket = new ThrottlingBucket(10, Duration(minutes: 1));
12+
expect(bucket.removeDrop(), true);
13+
});
14+
15+
test("doesn't send too many", () {
16+
ThrottlingBucket bucket = new ThrottlingBucket(10, Duration(minutes: 1));
17+
for (int i = 0; i < 10; i++) {
18+
expect(bucket.removeDrop(), true);
19+
}
20+
expect(bucket.removeDrop(), false);
21+
});
22+
});
23+
}

0 commit comments

Comments
 (0)