mixpanel 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -33,7 +33,7 @@ In your application_controller class add a method to instance mixpanel.
33
33
  before_filter :initialize_mixpanel
34
34
 
35
35
  def initialize_mixpanel
36
- @mixpanel = Mixpanel.new("YOUR_MIXPANEL_API_TOKEN", request.env)
36
+ @mixpanel = Mixpanel.new("YOUR_MIXPANEL_API_TOKEN", request.env, true)
37
37
  end
38
38
 
39
39
  Then in each request you want to track some event you can use:
@@ -54,12 +54,13 @@ To execute any javascript API call
54
54
 
55
55
  == Notes
56
56
 
57
- It is strongly recommended to call Mixpanel#track_event using an async lib
58
- like delayed job or similar, otherwise you will delay your server responses
59
- with mixpanel responses.
57
+ There are two forms of async operation:
58
+ * Using MixpanelMiddleware, events are queued via Mixpanel#append_event and inserted into a JavaScript block within the HTML response.
59
+ * Using Mixpanel.new(…, …, true), events are sent to a subprocess via a pipe and the sub process which asynchronously send events to Mixpanel. This process uses a single thread to upload events, and may start dropping events if your application generates them at a very high rate.
60
60
 
61
61
  == Collaborators and Maintainers
62
62
 
63
63
  * {Alvaro Gil}[https://github.com/zevarito] (Author)
64
64
  * {Nathan Baxter}[https://github.com/LogicWolfe]
65
65
  * {Jake Mallory}[https://github.com/tinomen]
66
+ * {Logan Bowers}[https://github.com/loganb]
@@ -2,10 +2,13 @@ require "open-uri"
2
2
  require 'base64'
3
3
  require 'json'
4
4
 
5
+ require 'thread'
6
+
5
7
  class Mixpanel
6
- def initialize(token, env)
8
+ def initialize(token, env, async = false)
7
9
  @token = token
8
10
  @env = env
11
+ @async = async
9
12
  clear_queue
10
13
  end
11
14
 
@@ -33,6 +36,35 @@ class Mixpanel
33
36
  def clear_queue
34
37
  @env["mixpanel_events"] = []
35
38
  end
39
+
40
+ class <<self
41
+ WORKER_MUTEX = Mutex.new
42
+
43
+ def worker
44
+ WORKER_MUTEX.synchronize do
45
+ @worker || (@worker = IO.popen(self.cmd, 'w'))
46
+ end
47
+ end
48
+
49
+ def dispose_worker(w)
50
+ WORKER_MUTEX.synchronize do
51
+ if(@worker == w)
52
+ @worker = nil
53
+ w.close
54
+ end
55
+ end
56
+ end
57
+
58
+ def cmd
59
+ @cmd || begin
60
+ require 'escape'
61
+ require 'rbconfig'
62
+ interpreter = File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['RUBY_SO_NAME'])
63
+ subprocess = File.join(File.dirname(__FILE__), 'mixpanel_subprocess.rb')
64
+ @cmd = Escape.shell_command([interpreter, subprocess])
65
+ end
66
+ end
67
+ end
36
68
 
37
69
  private
38
70
 
@@ -44,7 +76,17 @@ class Mixpanel
44
76
  data = Base64.encode64(JSON.generate(params)).gsub(/\n/,'')
45
77
  url = "http://api.mixpanel.com/track/?data=#{data}"
46
78
 
47
- open(url).read
79
+ if(@async)
80
+ w = Mixpanel.worker
81
+ begin
82
+ url << "\n"
83
+ w.write(url)
84
+ rescue Errno::EPIPE => e
85
+ Mixpanel.dispose_worker(w)
86
+ end
87
+ else
88
+ open(url).read
89
+ end
48
90
  end
49
91
 
50
92
  def build_event(event, properties)
@@ -0,0 +1,30 @@
1
+
2
+ require 'rubygems'
3
+ require 'mixpanel'
4
+ require 'open-uri'
5
+
6
+ require 'thread'
7
+
8
+ class Mixpanel::Subprocess
9
+ Q = Queue.new
10
+ ENDMARKER = Object.new
11
+
12
+ Thread.abort_on_exception = true
13
+ producer = Thread.new do
14
+ STDIN.each_line() do |url|
15
+ STDERR.puts("Dropped: #{url}") && next if Q.length > 10000
16
+ Q << url
17
+ end
18
+ Q << ENDMARKER
19
+ end
20
+
21
+ loop do
22
+ url = Q.pop
23
+ break if(url == ENDMARKER)
24
+ url.chomp!
25
+ next if(url.empty?) #for testing
26
+
27
+ open(url).read
28
+ end
29
+ producer.join
30
+ end
@@ -2,7 +2,7 @@ files = ['README.rdoc', 'LICENSE', 'Rakefile', 'mixpanel.gemspec', '{spec,lib}/*
2
2
 
3
3
  spec = Gem::Specification.new do |s|
4
4
  s.name = "mixpanel"
5
- s.version = "0.7.0"
5
+ s.version = "0.8.0"
6
6
  s.rubyforge_project = "mixpanel"
7
7
  s.description = "Simple lib to track events in Mixpanel service. It can be used in any rack based framework."
8
8
  s.author = "Alvaro Gil"
@@ -16,6 +16,7 @@ spec = Gem::Specification.new do |s|
16
16
  s.extra_rdoc_files = ["README.rdoc"]
17
17
  s.add_dependency 'json'
18
18
  s.add_dependency 'rack'
19
+ s.add_dependency 'escape'
19
20
  s.add_development_dependency 'rspec'
20
21
  s.add_development_dependency 'rack-test'
21
22
  s.add_development_dependency 'fakeweb'
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Mixpanel do
4
- before do
4
+ before(:each) do
5
5
  @mixpanel = Mixpanel.new(MIX_PANEL_TOKEN, @env = {"REMOTE_ADDR" => "127.0.0.1"})
6
6
  end
7
7
 
@@ -82,4 +82,32 @@ describe Mixpanel do
82
82
  end
83
83
  end
84
84
  end
85
+
86
+ context "Accessing Mixpanel asynchronously" do
87
+ it "should open a subprocess successfully" do
88
+ w = Mixpanel.worker
89
+ w.should == Mixpanel.worker
90
+ end
91
+
92
+ it "should be able to write lines to the worker" do
93
+ w = Mixpanel.worker
94
+
95
+ #On most systems this will exceed the pipe buffer size
96
+ 8.times do
97
+ 9000.times do
98
+ w.write("\n")
99
+ end
100
+ sleep 0.1
101
+ end
102
+ end
103
+
104
+ it "should dispose of a worker" do
105
+ w = Mixpanel.worker
106
+ Mixpanel.dispose_worker(w)
107
+
108
+ w.closed?.should == true
109
+ w2 = Mixpanel.worker
110
+ w2.should_not == w
111
+ end
112
+ end
85
113
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mixpanel
3
3
  version: !ruby/object:Gem::Version
4
- hash: 3
4
+ hash: 63
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 7
8
+ - 8
9
9
  - 0
10
- version: 0.7.0
10
+ version: 0.8.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Alvaro Gil
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-11-26 00:00:00 -02:00
18
+ date: 2011-01-25 00:00:00 -02:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -47,7 +47,7 @@ dependencies:
47
47
  type: :runtime
48
48
  version_requirements: *id002
49
49
  - !ruby/object:Gem::Dependency
50
- name: rspec
50
+ name: escape
51
51
  prerelease: false
52
52
  requirement: &id003 !ruby/object:Gem::Requirement
53
53
  none: false
@@ -58,10 +58,10 @@ dependencies:
58
58
  segments:
59
59
  - 0
60
60
  version: "0"
61
- type: :development
61
+ type: :runtime
62
62
  version_requirements: *id003
63
63
  - !ruby/object:Gem::Dependency
64
- name: rack-test
64
+ name: rspec
65
65
  prerelease: false
66
66
  requirement: &id004 !ruby/object:Gem::Requirement
67
67
  none: false
@@ -75,7 +75,7 @@ dependencies:
75
75
  type: :development
76
76
  version_requirements: *id004
77
77
  - !ruby/object:Gem::Dependency
78
- name: fakeweb
78
+ name: rack-test
79
79
  prerelease: false
80
80
  requirement: &id005 !ruby/object:Gem::Requirement
81
81
  none: false
@@ -89,7 +89,7 @@ dependencies:
89
89
  type: :development
90
90
  version_requirements: *id005
91
91
  - !ruby/object:Gem::Dependency
92
- name: nokogiri
92
+ name: fakeweb
93
93
  prerelease: false
94
94
  requirement: &id006 !ruby/object:Gem::Requirement
95
95
  none: false
@@ -102,6 +102,20 @@ dependencies:
102
102
  version: "0"
103
103
  type: :development
104
104
  version_requirements: *id006
105
+ - !ruby/object:Gem::Dependency
106
+ name: nokogiri
107
+ prerelease: false
108
+ requirement: &id007 !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ hash: 3
114
+ segments:
115
+ - 0
116
+ version: "0"
117
+ type: :development
118
+ version_requirements: *id007
105
119
  description: Simple lib to track events in Mixpanel service. It can be used in any rack based framework.
106
120
  email: zevarito@gmail.com
107
121
  executables: []
@@ -121,6 +135,7 @@ files:
121
135
  - spec/support/rack_apps.rb
122
136
  - lib/mixpanel/mixpanel.rb
123
137
  - lib/mixpanel/mixpanel_middleware.rb
138
+ - lib/mixpanel/mixpanel_subprocess.rb
124
139
  - lib/mixpanel.rb
125
140
  has_rdoc: true
126
141
  homepage: http://cuboxsa.com