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

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

Issue 2989763002: Update charted to 0.4.8 and roll (Closed)
Patch Set: Removed Cutch from list of reviewers Created 3 years, 4 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 | « packages/usage/example/ga.dart ('k') | packages/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
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library usage_impl;
6
7 import 'dart:async'; 5 import 'dart:async';
8 import 'dart:math' as math; 6 import 'dart:math' as math;
9 7
10 import 'uuid.dart';
11 import '../usage.dart'; 8 import '../usage.dart';
12 9 import '../uuid/uuid.dart';
13 final int _MAX_EXCEPTION_LENGTH = 100;
14 10
15 String postEncode(Map<String, dynamic> map) { 11 String postEncode(Map<String, dynamic> map) {
16 // &foo=bar 12 // &foo=bar
17 return map.keys.map((key) { 13 return map.keys.map((key) {
18 String value = '${map[key]}'; 14 String value = '${map[key]}';
19 return "${key}=${Uri.encodeComponent(value)}"; 15 return "${key}=${Uri.encodeComponent(value)}";
20 }).join('&'); 16 }).join('&');
21 } 17 }
22 18
23 /** 19 /**
24 * A throttling algorithim. This models the throttling after a bucket with 20 * A throttling algorithm. 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 21 * 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 22 * 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 23 * operation is performed. If not the operation is skipped. This algorithm
28 * lets operations be peformed in bursts without throttling, but holds the 24 * lets operations be performed in bursts without throttling, but holds the
29 * overall average rate of operations to 1 per second. 25 * overall average rate of operations to 1 per second.
30 */ 26 */
31 class ThrottlingBucket { 27 class ThrottlingBucket {
32 final int startingCount; 28 final int startingCount;
33 int drops; 29 int drops;
34 int _lastReplenish; 30 int _lastReplenish;
35 31
36 ThrottlingBucket(this.startingCount) { 32 ThrottlingBucket(this.startingCount) {
37 drops = startingCount; 33 drops = startingCount;
38 _lastReplenish = new DateTime.now().millisecondsSinceEpoch; 34 _lastReplenish = new DateTime.now().millisecondsSinceEpoch;
(...skipping 14 matching lines...) Expand all
53 int now = new DateTime.now().millisecondsSinceEpoch; 49 int now = new DateTime.now().millisecondsSinceEpoch;
54 50
55 if (_lastReplenish + 1000 >= now) { 51 if (_lastReplenish + 1000 >= now) {
56 int inc = (now - _lastReplenish) ~/ 1000; 52 int inc = (now - _lastReplenish) ~/ 1000;
57 drops = math.min(drops + inc, startingCount); 53 drops = math.min(drops + inc, startingCount);
58 _lastReplenish += (1000 * inc); 54 _lastReplenish += (1000 * inc);
59 } 55 }
60 } 56 }
61 } 57 }
62 58
63 abstract class AnalyticsImpl extends Analytics { 59 class AnalyticsImpl implements Analytics {
64 static const String _GA_URL = 'https://www.google-analytics.com/collect'; 60 static const String _defaultAnalyticsUrl =
61 'https://www.google-analytics.com/collect';
65 62
66 /// Tracking ID / Property ID. 63 @override
67 final String trackingId; 64 final String trackingId;
65 @override
66 final String applicationName;
67 @override
68 final String applicationVersion;
68 69
69 final PersistentProperties properties; 70 final PersistentProperties properties;
70 final PostHandler postHandler; 71 final PostHandler postHandler;
71 72
72 final ThrottlingBucket _bucket = new ThrottlingBucket(20); 73 final ThrottlingBucket _bucket = new ThrottlingBucket(20);
73 final Map<String, dynamic> _variableMap = {}; 74 final Map<String, dynamic> _variableMap = {};
74 75
75 final List<Future> _futures = []; 76 final List<Future> _futures = [];
76 77
78 @override
79 AnalyticsOpt analyticsOpt = AnalyticsOpt.optOut;
80
81 String _url;
82
83 StreamController<Map<String, dynamic>> _sendController =
84 new StreamController.broadcast(sync: true);
85
77 AnalyticsImpl(this.trackingId, this.properties, this.postHandler, 86 AnalyticsImpl(this.trackingId, this.properties, this.postHandler,
78 {String applicationName, String applicationVersion}) { 87 {this.applicationName, this.applicationVersion, String analyticsUrl}) {
79 assert(trackingId != null); 88 assert(trackingId != null);
80 89
81 if (applicationName != null) setSessionValue('an', applicationName); 90 if (applicationName != null) setSessionValue('an', applicationName);
82 if (applicationVersion != null) setSessionValue('av', applicationVersion); 91 if (applicationVersion != null) setSessionValue('av', applicationVersion);
92
93 _url = analyticsUrl ?? _defaultAnalyticsUrl;
83 } 94 }
84 95
85 bool get optIn => properties['optIn'] == true; 96 bool _firstRun;
86 97
87 set optIn(bool value) { 98 @override
88 properties['optIn'] = value; 99 bool get firstRun {
100 if (_firstRun == null) {
101 _firstRun = properties['firstRun'] == null;
102
103 if (properties['firstRun'] != false) {
104 properties['firstRun'] = false;
105 }
106 }
107
108 return _firstRun;
89 } 109 }
90 110
91 bool get hasSetOptIn => properties['optIn'] != null; 111 @override
112 bool get enabled {
113 bool optIn = analyticsOpt == AnalyticsOpt.optIn;
114 return optIn
115 ? properties['enabled'] == true
116 : properties['enabled'] != false;
117 }
92 118
93 Future sendScreenView(String viewName) { 119 @override
120 set enabled(bool value) {
121 properties['enabled'] = value;
122 }
123
124 @override
125 Future sendScreenView(String viewName, {Map<String, String> parameters}) {
94 Map<String, dynamic> args = {'cd': viewName}; 126 Map<String, dynamic> args = {'cd': viewName};
127 if (parameters != null) {
128 args.addAll(parameters);
129 }
95 return _sendPayload('screenview', args); 130 return _sendPayload('screenview', args);
96 } 131 }
97 132
98 Future sendEvent(String category, String action, {String label, int value}) { 133 @override
99 if (!optIn) return new Future.value(); 134 Future sendEvent(String category, String action,
100 135 {String label, int value, Map<String, String> parameters}) {
101 Map<String, dynamic> args = {'ec': category, 'ea': action}; 136 Map<String, dynamic> args = {'ec': category, 'ea': action};
102 if (label != null) args['el'] = label; 137 if (label != null) args['el'] = label;
103 if (value != null) args['ev'] = value; 138 if (value != null) args['ev'] = value;
139 if (parameters != null) {
140 args.addAll(parameters);
141 }
104 return _sendPayload('event', args); 142 return _sendPayload('event', args);
105 } 143 }
106 144
145 @override
107 Future sendSocial(String network, String action, String target) { 146 Future sendSocial(String network, String action, String target) {
108 if (!optIn) return new Future.value();
109
110 Map<String, dynamic> args = {'sn': network, 'sa': action, 'st': target}; 147 Map<String, dynamic> args = {'sn': network, 'sa': action, 'st': target};
111 return _sendPayload('social', args); 148 return _sendPayload('social', args);
112 } 149 }
113 150
114 Future sendTiming(String variableName, int time, {String category, 151 @override
115 String label}) { 152 Future sendTiming(String variableName, int time,
116 if (!optIn) return new Future.value(); 153 {String category, String label}) {
117
118 Map<String, dynamic> args = {'utv': variableName, 'utt': time}; 154 Map<String, dynamic> args = {'utv': variableName, 'utt': time};
119 if (label != null) args['utl'] = label; 155 if (label != null) args['utl'] = label;
120 if (category != null) args['utc'] = category; 156 if (category != null) args['utc'] = category;
121 return _sendPayload('timing', args); 157 return _sendPayload('timing', args);
122 } 158 }
123 159
124 AnalyticsTimer startTimer(String variableName, {String category, String label} ) { 160 @override
125 return new AnalyticsTimer(this, 161 AnalyticsTimer startTimer(String variableName,
126 variableName, category: category, label: label); 162 {String category, String label}) {
163 return new AnalyticsTimer(this, variableName,
164 category: category, label: label);
127 } 165 }
128 166
167 @override
129 Future sendException(String description, {bool fatal}) { 168 Future sendException(String description, {bool fatal}) {
130 if (!optIn) return new Future.value(); 169 // We trim exceptions to a max length; google analytics will apply it's own
170 // truncation, likely around 150 chars or so.
171 const int maxExceptionLength = 1000;
131 172
132 // In order to ensure that the client of this API is not sending any PII 173 // 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 174 // data, we strip out any stack trace that may reference a path on the
134 // user's drive (file:/...). 175 // user's drive (file:/...).
135 if (description.contains('file:/')) { 176 if (description.contains('file:/')) {
136 description = description.substring(0, description.indexOf('file:/')); 177 description = description.substring(0, description.indexOf('file:/'));
137 } 178 }
138 179
139 if (description != null && description.length > _MAX_EXCEPTION_LENGTH) { 180 description = description.replaceAll('\n', '; ');
140 description = description.substring(0, _MAX_EXCEPTION_LENGTH); 181
182 if (description.length > maxExceptionLength) {
183 description = description.substring(0, maxExceptionLength);
141 } 184 }
142 185
143 Map<String, dynamic> args = {'exd': description}; 186 Map<String, dynamic> args = {'exd': description};
144 if (fatal != null && fatal) args['exf'] = '1'; 187 if (fatal != null && fatal) args['exf'] = '1';
145 return _sendPayload('exception', args); 188 return _sendPayload('exception', args);
146 } 189 }
147 190
191 @override
192 dynamic getSessionValue(String param) => _variableMap[param];
193
194 @override
148 void setSessionValue(String param, dynamic value) { 195 void setSessionValue(String param, dynamic value) {
149 if (value == null) { 196 if (value == null) {
150 _variableMap.remove(param); 197 _variableMap.remove(param);
151 } else { 198 } else {
152 _variableMap[param] = value; 199 _variableMap[param] = value;
153 } 200 }
154 } 201 }
155 202
203 @override
204 Stream<Map<String, dynamic>> get onSend => _sendController.stream;
205
206 @override
156 Future waitForLastPing({Duration timeout}) { 207 Future waitForLastPing({Duration timeout}) {
157 Future f = Future.wait(_futures).catchError((e) => null); 208 Future f = Future.wait(_futures).catchError((e) => null);
158 209
159 if (timeout != null) { 210 if (timeout != null) {
160 f = f.timeout(timeout, onTimeout: () => null); 211 f = f.timeout(timeout, onTimeout: () => null);
161 } 212 }
162 213
163 return f; 214 return f;
164 } 215 }
165 216
217 @override
218 void close() => postHandler.close();
219
220 @override
221 String get clientId => properties['clientId'] ??= new Uuid().generateV4();
222
166 /** 223 /**
167 * Anonymous Client ID. The value of this field should be a random UUID v4. 224 * Send raw data to analytics. Callers should generally use one of the typed
225 * methods (`sendScreenView`, `sendEvent`, ...).
226 *
227 * Valid values for [hitType] are: 'pageview', 'screenview', 'event',
228 * 'transaction', 'item', 'social', 'exception', and 'timing'.
168 */ 229 */
169 String get _clientId => properties['clientId']; 230 Future sendRaw(String hitType, Map<String, dynamic> args) {
170 231 return _sendPayload(hitType, args);
171 void _initClientId() {
172 if (_clientId == null) {
173 properties['clientId'] = new Uuid().generateV4();
174 }
175 } 232 }
176 233
177 // Valid values for [hitType] are: 'pageview', 'screenview', 'event', 234 /**
178 // 'transaction', 'item', 'social', 'exception', and 'timing'. 235 * Valid values for [hitType] are: 'pageview', 'screenview', 'event',
236 * 'transaction', 'item', 'social', 'exception', and 'timing'.
237 */
179 Future _sendPayload(String hitType, Map<String, dynamic> args) { 238 Future _sendPayload(String hitType, Map<String, dynamic> args) {
239 if (!enabled) return new Future.value();
240
180 if (_bucket.removeDrop()) { 241 if (_bucket.removeDrop()) {
181 _initClientId();
182
183 _variableMap.forEach((key, value) { 242 _variableMap.forEach((key, value) {
184 args[key] = value; 243 args[key] = value;
185 }); 244 });
186 245
187 args['v'] = '1'; // protocol version 246 args['v'] = '1'; // protocol version
188 args['tid'] = trackingId; 247 args['tid'] = trackingId;
189 args['cid'] = _clientId; 248 args['cid'] = clientId;
190 args['t'] = hitType; 249 args['t'] = hitType;
191 250
192 return _recordFuture(postHandler.sendPost(_GA_URL, args)); 251 _sendController.add(args);
252
253 return _recordFuture(postHandler.sendPost(_url, args));
193 } else { 254 } else {
194 return new Future.value(); 255 return new Future.value();
195 } 256 }
196 } 257 }
197 258
198 Future _recordFuture(Future f) { 259 Future _recordFuture(Future f) {
199 _futures.add(f); 260 _futures.add(f);
200 return f.whenComplete(() => _futures.remove(f)); 261 return f.whenComplete(() => _futures.remove(f));
201 } 262 }
202 } 263 }
203 264
204 /** 265 /**
205 * A persistent key/value store. An [AnalyticsImpl] instance expects to have one 266 * 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` 267 * of these injected into it. There are default implementations for `dart:io`
207 * and `dart:html` clients. 268 * and `dart:html` clients.
208 * 269 *
209 * The [name] paramater is used to uniquely store these properties on disk / 270 * The [name] parameter is used to uniquely store these properties on disk /
210 * persistent storage. 271 * persistent storage.
211 */ 272 */
212 abstract class PersistentProperties { 273 abstract class PersistentProperties {
213 final String name; 274 final String name;
214 275
215 PersistentProperties(this.name); 276 PersistentProperties(this.name);
216 277
217 dynamic operator[](String key); 278 dynamic operator [](String key);
218 void operator[]=(String key, dynamic value); 279 void operator []=(String key, dynamic value);
280
281 /// Re-read settings from the backing store. This may be a no-op on some
282 /// platforms.
283 void syncSettings();
219 } 284 }
220 285
221 /** 286 /**
222 * A utility class to perform HTTP POSTs. An [AnalyticsImpl] instance expects to 287 * 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 288 * have one of these injected into it. There are default implementations for
224 * `dart:io` and `dart:html` clients. 289 * `dart:io` and `dart:html` clients.
225 * 290 *
226 * The POST information should be sent on a best-effort basis. The `Future` from 291 * 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 292 * [sendPost] should complete when the operation is finished, but failures to
228 * send the information should be silent. 293 * send the information should be silent.
229 */ 294 */
230 abstract class PostHandler { 295 abstract class PostHandler {
231 Future sendPost(String url, Map<String, dynamic> parameters); 296 Future sendPost(String url, Map<String, dynamic> parameters);
297
298 /// Free any used resources.
299 void close();
232 } 300 }
OLDNEW
« no previous file with comments | « packages/usage/example/ga.dart ('k') | packages/usage/lib/src/usage_impl_html.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698