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

Side by Side Diff: usage/lib/src/usage_impl.dart

Issue 1400473008: Roll Observatory packages and add a roll script (Closed) Base URL: git@github.com:dart-lang/observatory_pub_packages.git@master
Patch Set: Created 5 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 unified diff | Download patch
« no previous file with comments | « unittest/pubspec.yaml ('k') | usage/lib/src/usage_impl_html.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2014, 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 library usage_impl;
6
7 import 'dart:async';
8 import 'dart:math' as math;
9
10 import 'uuid.dart';
11 import '../usage.dart';
12
13 final int _MAX_EXCEPTION_LENGTH = 100;
14
15 String postEncode(Map<String, dynamic> map) {
16 // &foo=bar
17 return map.keys.map((key) {
18 String value = '${map[key]}';
19 return "${key}=${Uri.encodeComponent(value)}";
20 }).join('&');
21 }
22
23 /**
24 * A throttling algorithim. This models the throttling after a bucket with
25 * water dripping into it at the rate of 1 drop per second. If the bucket has
26 * water when an operation is requested, 1 drop of water is removed and the
27 * operation is performed. If not the operation is skipped. This algorithim
28 * lets operations be peformed in bursts without throttling, but holds the
29 * overall average rate of operations to 1 per second.
30 */
31 class ThrottlingBucket {
32 final int startingCount;
33 int drops;
34 int _lastReplenish;
35
36 ThrottlingBucket(this.startingCount) {
37 drops = startingCount;
38 _lastReplenish = new DateTime.now().millisecondsSinceEpoch;
39 }
40
41 bool removeDrop() {
42 _checkReplenish();
43
44 if (drops <= 0) {
45 return false;
46 } else {
47 drops--;
48 return true;
49 }
50 }
51
52 void _checkReplenish() {
53 int now = new DateTime.now().millisecondsSinceEpoch;
54
55 if (_lastReplenish + 1000 >= now) {
56 int inc = (now - _lastReplenish) ~/ 1000;
57 drops = math.min(drops + inc, startingCount);
58 _lastReplenish += (1000 * inc);
59 }
60 }
61 }
62
63 abstract class AnalyticsImpl extends Analytics {
64 static const String _GA_URL = 'https://www.google-analytics.com/collect';
65
66 /// Tracking ID / Property ID.
67 final String trackingId;
68
69 final PersistentProperties properties;
70 final PostHandler postHandler;
71
72 final ThrottlingBucket _bucket = new ThrottlingBucket(20);
73 final Map<String, dynamic> _variableMap = {};
74
75 final List<Future> _futures = [];
76
77 AnalyticsImpl(this.trackingId, this.properties, this.postHandler,
78 {String applicationName, String applicationVersion}) {
79 assert(trackingId != null);
80
81 if (applicationName != null) setSessionValue('an', applicationName);
82 if (applicationVersion != null) setSessionValue('av', applicationVersion);
83 }
84
85 bool get optIn => properties['optIn'] == true;
86
87 set optIn(bool value) {
88 properties['optIn'] = value;
89 }
90
91 bool get hasSetOptIn => properties['optIn'] != null;
92
93 Future sendScreenView(String viewName) {
94 Map args = {'cd': viewName};
95 return _sendPayload('screenview', args);
96 }
97
98 Future sendEvent(String category, String action, {String label, int value}) {
99 if (!optIn) return new Future.value();
100
101 Map args = {'ec': category, 'ea': action};
102 if (label != null) args['el'] = label;
103 if (value != null) args['ev'] = value;
104 return _sendPayload('event', args);
105 }
106
107 Future sendSocial(String network, String action, String target) {
108 if (!optIn) return new Future.value();
109
110 Map args = {'sn': network, 'sa': action, 'st': target};
111 return _sendPayload('social', args);
112 }
113
114 Future sendTiming(String variableName, int time, {String category,
115 String label}) {
116 if (!optIn) return new Future.value();
117
118 Map args = {'utv': variableName, 'utt': time};
119 if (label != null) args['utl'] = label;
120 if (category != null) args['utc'] = category;
121 return _sendPayload('timing', args);
122 }
123
124 AnalyticsTimer startTimer(String variableName, {String category, String label} ) {
125 return new AnalyticsTimer(this,
126 variableName, category: category, label: label);
127 }
128
129 Future sendException(String description, {bool fatal}) {
130 if (!optIn) return new Future.value();
131
132 // In order to ensure that the client of this API is not sending any PII
133 // data, we strip out any stack trace that may reference a path on the
134 // user's drive (file:/...).
135 if (description.contains('file:/')) {
136 description = description.substring(0, description.indexOf('file:/'));
137 }
138
139 if (description != null && description.length > _MAX_EXCEPTION_LENGTH) {
140 description = description.substring(0, _MAX_EXCEPTION_LENGTH);
141 }
142
143 Map args = {'exd': description};
144 if (fatal != null && fatal) args['exf'] = '1';
145 return _sendPayload('exception', args);
146 }
147
148 void setSessionValue(String param, dynamic value) {
149 if (value == null) {
150 _variableMap.remove(param);
151 } else {
152 _variableMap[param] = value;
153 }
154 }
155
156 Future waitForLastPing({Duration timeout}) {
157 Future f = Future.wait(_futures).catchError((e) => null);
158
159 if (timeout != null) {
160 f = f.timeout(timeout, onTimeout: () => null);
161 }
162
163 return f;
164 }
165
166 /**
167 * Anonymous Client ID. The value of this field should be a random UUID v4.
168 */
169 String get _clientId => properties['clientId'];
170
171 void _initClientId() {
172 if (_clientId == null) {
173 properties['clientId'] = new Uuid().generateV4();
174 }
175 }
176
177 // Valid values for [hitType] are: 'pageview', 'screenview', 'event',
178 // 'transaction', 'item', 'social', 'exception', and 'timing'.
179 Future _sendPayload(String hitType, Map args) {
180 if (_bucket.removeDrop()) {
181 _initClientId();
182
183 _variableMap.forEach((key, value) {
184 args[key] = value;
185 });
186
187 args['v'] = '1'; // protocol version
188 args['tid'] = trackingId;
189 args['cid'] = _clientId;
190 args['t'] = hitType;
191
192 return _recordFuture(postHandler.sendPost(_GA_URL, args));
193 } else {
194 return new Future.value();
195 }
196 }
197
198 Future _recordFuture(Future f) {
199 _futures.add(f);
200 return f.whenComplete(() => _futures.remove(f));
201 }
202 }
203
204 /**
205 * A persistent key/value store. An [AnalyticsImpl] instance expects to have one
206 * of these injected into it. There are default implementations for `dart:io`
207 * and `dart:html` clients.
208 *
209 * The [name] paramater is used to uniquely store these properties on disk /
210 * persistent storage.
211 */
212 abstract class PersistentProperties {
213 final String name;
214
215 PersistentProperties(this.name);
216
217 dynamic operator[](String key);
218 void operator[]=(String key, dynamic value);
219 }
220
221 /**
222 * A utility class to perform HTTP POSTs. An [AnalyticsImpl] instance expects to
223 * have one of these injected into it. There are default implementations for
224 * `dart:io` and `dart:html` clients.
225 *
226 * The POST information should be sent on a best-effort basis. The `Future` from
227 * [sendPost] should complete when the operation is finished, but failures to
228 * send the information should be silent.
229 */
230 abstract class PostHandler {
231 Future sendPost(String url, Map<String, String> parameters);
232 }
OLDNEW
« no previous file with comments | « unittest/pubspec.yaml ('k') | usage/lib/src/usage_impl_html.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698