First cut of basic functionality

This CL addresses comments from the previous Pull Request https://github.com/lukechurch/dart-microlytics/pull/1

It can be used to reporting usage and performance data to Google Analytics.

Known issues:

-> unittest library is still used
-> some comments are still not in full sentences

R=ahe@google.com, danrubel@google.com

Review URL: https://codereview.chromium.org//515993003

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@39914 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
lukechurch@google.com 2014-09-05 14:00:08 +00:00
parent 700df9e917
commit 710bf6f401
8 changed files with 324 additions and 0 deletions

View file

@ -0,0 +1,26 @@
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. 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:microlytics/channels.dart';
import 'package:microlytics/io_channels.dart';
import 'package:microlytics/microlytics.dart';
void main(List<String> arguments) {
// Create the channel that will be used to communicate to analytics.
var channel = new RateLimitingBufferedChannel(
new HttpClientChannel(), packetsPerSecond: 1.0);
if (arguments.length != 1) {
print("usage: dart simple.dart GA-Client-ID");
return;
}
final String clientID = arguments.single;
// Create the logger.
var lg = new AnalyticsLogger(channel, "555", clientID, "test", "1.2");
// Send some messages.
lg.logAnonymousEvent("hello", "world");
lg.logAnonymousTiming("loader", "var", 42);
}

View file

@ -0,0 +1,55 @@
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library microlytics.channels;
import 'dart:async';
const String ANALYTICS_URL = "https://ssl.google-analytics.com/collect";
abstract class Channel {
void sendData(String data);
void shutdown() {}
}
/// [Channel] that implements a leaky bucket
/// algorithm to provide rate limiting.
/// See [http://en.wikipedia.org/wiki/Leaky_bucket].
class RateLimitingBufferedChannel extends Channel {
final List<String> _buffer = <String>[];
final Channel _innerChannel;
final int _bufferSizeLimit;
Timer _timer;
RateLimitingBufferedChannel(
this._innerChannel,
{int bufferSizeLimit: 10,
double packetsPerSecond: 1.0})
: this._bufferSizeLimit = bufferSizeLimit {
if (!(packetsPerSecond > 0)) {
throw new ArgumentError("packetsPerSecond must be larger than zero.");
}
int transmitDelay = (1000 / packetsPerSecond).floor();
_timer = new Timer.periodic(
new Duration(milliseconds: transmitDelay), _onTimerTick);
}
void _onTimerTick(_) {
if (_buffer.length > 0) {
String item = _buffer.removeLast();
_innerChannel.sendData(item);
}
}
void sendData(String data) {
if (_buffer.length >= _bufferSizeLimit) return;
_buffer.add(data);
}
void shutdown() {
_timer.cancel();
_innerChannel.shutdown();
}
}

View file

@ -0,0 +1,14 @@
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library microlytics.html_channels;
import 'dart:html';
import 'channels.dart';
class HttpRequestChannel extends Channel {
void sendData(String data) {
HttpRequest.request(ANALYTICS_URL, method: "POST", sendData: data);
}
}

View file

@ -0,0 +1,21 @@
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library microlytics.io_channels;
import 'dart:io';
import 'channels.dart';
class HttpClientChannel extends Channel {
void sendData(String data) {
HttpClient client = new HttpClient();
client.postUrl(Uri.parse(ANALYTICS_URL)).then((HttpClientRequest req) {
req.write(data);
return req.close();
}).then((HttpClientResponse response) {
response.drain();
});
}
}

View file

@ -0,0 +1,61 @@
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library microlytics;
import 'channels.dart';
/// Very limited implementation of an API to report usage to Google Analytics.
/// No Personally Identifiable Information must ever be passed to this class.
class AnalyticsLogger {
final Channel _channel;
final String _clientID;
final String _analyticsID;
final String _appName;
final String _appVersion;
final String _messagePrefix; //Computed prefix for analytics messages
/// Create a new logger
/// [channel] represents how this is going to be sent, this would typically
/// be a [RateLimitingBufferedChannel] wrapping either a [HttpRequestChannel]
/// or a [HttpClientChannel].
/// [clientID] is a version 4 UUID associated with the site or app.
/// [appName] is an application name.
/// [appVersion] is a verion string.
AnalyticsLogger(Channel channel, String clientID, String analyticsID,
String appName, String appVersion)
: this._channel = channel,
this._clientID = clientID,
this._analyticsID = analyticsID,
this._appName = appName,
this._appVersion = appVersion,
this._messagePrefix =
"v=1"
"&tid=$analyticsID"
"&cid=$clientID"
"&an=$appName"
"&av=$appVersion";
void logAnonymousTiming(String category, String variable, int ms) {
category = Uri.encodeComponent(category);
variable = Uri.encodeComponent(variable);
_channel.sendData(
"${this._messagePrefix}"
"&t=timing"
"&utc=$category"
"&utv=$variable"
"&utt=$ms");
}
void logAnonymousEvent(String category, String event) {
category = Uri.encodeComponent(category);
event = Uri.encodeComponent(event);
_channel.sendData(
"${this._messagePrefix}"
"&t=event"
"&ec=$category"
"&ea=$event");
}
}

View file

@ -0,0 +1,8 @@
# Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
name: microlytics
description: A minimal implementation of the Analytics API in pure Dart
dev_dependencies:
unittest: any

View file

@ -0,0 +1,120 @@
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library microlytics.test;
import 'package:expect/expect.dart';
import 'package:microlytics/microlytics.dart';
import 'test_channel.dart';
void main() {
testBasicEventRead();
testBasicNegativeEventRead();
testBasicTimingRead();
testBasicTimingMultiread();
}
void testBasicEventRead() {
TestChannel c = new TestChannel();
AnalyticsLogger logger = new AnalyticsLogger(
c,
"2cfac780-31e2-11e4-8c21-0800200c9a66",
"UA-53895644-1",
"TestApp",
"0.42");
logger.logAnonymousEvent("video", "play");
Expect.isTrue(c.contains(
"v=1"
"&tid=UA-53895644-1"
"&cid=2cfac780-31e2-11e4-8c21-0800200c9a66"
"&an=TestApp"
"&av=0.42"
"&t=event"
"&ec=video"
"&ea=play"));
}
void testBasicNegativeEventRead() {
TestChannel c = new TestChannel();
AnalyticsLogger logger = new AnalyticsLogger(
c,
"2cfac780-31e2-11e4-8c21-0800200c9a66",
"UA-53895644-1",
"TestApp",
"0.42");
logger.logAnonymousEvent("video", "play");
Expect.isFalse(c.contains(
"v=1"
"&tid=UA-53895644-1"
"&cid=2cfac780-31e2-11e4-8c21-0800200c9a66"
"&an=TestApp"
"&av=XXX"
"&t=event"
"&ec=video"
"&ea=play"));
}
void testBasicTimingRead() {
TestChannel c = new TestChannel();
AnalyticsLogger logger = new AnalyticsLogger(
c,
"2cfac780-31e2-11e4-8c21-0800200c9a66",
"UA-53895644-1",
"TestApp",
"0.42");
logger.logAnonymousTiming("video", "delay", 157);
Expect.isTrue(c.contains(
"v=1"
"&tid=UA-53895644-1"
"&cid=2cfac780-31e2-11e4-8c21-0800200c9a66"
"&an=TestApp"
"&av=0.42"
"&t=timing"
"&utc=video"
"&utv=delay"
"&utt=157"));
}
void testBasicTimingMultiread() {
TestChannel c = new TestChannel();
AnalyticsLogger logger = new AnalyticsLogger(
c,
"2cfac780-31e2-11e4-8c21-0800200c9a66",
"UA-53895644-1",
"TestApp",
"0.42");
logger.logAnonymousTiming("video", "delay", 159);
logger.logAnonymousTiming("video", "delay", 152);
Expect.isTrue(c.contains(
"v=1"
"&tid=UA-53895644-1"
"&cid=2cfac780-31e2-11e4-8c21-0800200c9a66"
"&an=TestApp"
"&av=0.42"
"&t=timing"
"&utc=video"
"&utv=delay"
"&utt=152"));
Expect.isTrue(c.contains(
"v=1"
"&tid=UA-53895644-1"
"&cid=2cfac780-31e2-11e4-8c21-0800200c9a66"
"&an=TestApp"
"&av=0.42"
"&t=timing"
"&utc=video"
"&utv=delay"
"&utt=159"));
Expect.isFalse(c.contains(
"v=1"
"&tid=UA-53895644-1"
"&cid=2cfac780-31e2-11e4-8c21-0800200c9a66"
"&an=TestApp"
"&av=0.42"
"&t=timing"
"&utc=video"
"&utv=delay"
"&utt=19"));
}

View file

@ -0,0 +1,19 @@
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library microlytics.test_channel;
import 'package:microlytics/channels.dart';
class TestChannel extends Channel {
List<String> _channelLog = [];
void sendData(String data) {
_channelLog.add(data);
}
bool contains(String data) {
return _channelLog.contains(data);
}
}