StatsCollect 0.1.1.20101220 → 0.2.0.20110830
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +98 -0
- data/LICENSE +1 -1
- data/ReleaseInfo +8 -8
- data/StatsCollect.conf.rb.example +43 -2
- data/bin/StatsCollect.rb +1 -1
- data/lib/StatsCollect/Backends/MySQL.rb +253 -41
- data/lib/StatsCollect/Backends/Terminal.rb +39 -27
- data/lib/StatsCollect/Locations/AddThis.rb +2 -2
- data/lib/StatsCollect/Locations/CSV.rb +106 -0
- data/lib/StatsCollect/Locations/Facebook.rb +9 -5
- data/lib/StatsCollect/Locations/FacebookArtist.rb +4 -2
- data/lib/StatsCollect/Locations/FacebookLike.rb +1 -1
- data/lib/StatsCollect/Locations/GoogleGroup.rb +103 -0
- data/lib/StatsCollect/Locations/GoogleSearch.rb +1 -1
- data/lib/StatsCollect/Locations/MySpace.rb +63 -12
- data/lib/StatsCollect/Locations/RB.rb +64 -0
- data/lib/StatsCollect/Locations/ReverbNation.rb +34 -12
- data/lib/StatsCollect/Locations/Tweets.rb +1 -1
- data/lib/StatsCollect/Locations/Twitter.rb +1 -1
- data/lib/StatsCollect/Locations/Youtube.rb +12 -8
- data/lib/StatsCollect/Notifiers/Custom.rb +25 -0
- data/lib/StatsCollect/Notifiers/LogFile.rb +27 -0
- data/lib/StatsCollect/Notifiers/None.rb +1 -1
- data/lib/StatsCollect/Notifiers/SendMail.rb +1 -1
- data/lib/StatsCollect/Stats.rb +115 -74
- data/lib/StatsCollect/StatsOrdersProxy.rb +54 -0
- data/lib/StatsCollect/StatsProxy.rb +47 -6
- metadata +16 -10
- data/TODO +0 -2
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c)
|
2
|
+
# Copyright (c) 2010 - 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
3
|
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
4
|
#++
|
5
5
|
|
@@ -31,12 +31,14 @@ module StatsCollect
|
|
31
31
|
lUserID = lHomePage.uri.to_s.match(/^http:\/\/www\.reverbnation\.com\/artist\/control_room\/(\d*)$/)[1]
|
32
32
|
if ((oStatsProxy.isCategoryIncluded?('Song plays')) or
|
33
33
|
(oStatsProxy.isCategoryIncluded?('Song downloads')) or
|
34
|
-
(oStatsProxy.isCategoryIncluded?('Video plays')) or
|
35
34
|
(oStatsProxy.isCategoryIncluded?('Song play ratio')) or
|
36
35
|
(oStatsProxy.isCategoryIncluded?('Song likes')) or
|
37
36
|
(oStatsProxy.isCategoryIncluded?('Song dislikes')))
|
38
37
|
getPlays(oStatsProxy, lMechanizeAgent, lUserID)
|
39
38
|
end
|
39
|
+
if (oStatsProxy.isCategoryIncluded?('Video plays'))
|
40
|
+
getVideos(oStatsProxy, lMechanizeAgent, lUserID)
|
41
|
+
end
|
40
42
|
if ((oStatsProxy.isObjectIncluded?('Global')) and
|
41
43
|
((oStatsProxy.isCategoryIncluded?('Chart position genre')) or
|
42
44
|
(oStatsProxy.isCategoryIncluded?('Chart position global')) or
|
@@ -54,8 +56,8 @@ module StatsCollect
|
|
54
56
|
# * *iUserID* (_String_): The ReverbNation user ID
|
55
57
|
def getPlays(oStatsProxy, iMechanizeAgent, iUserID)
|
56
58
|
# Get the Ajax stats table
|
57
|
-
lStatsTable = iMechanizeAgent.get("http://www.reverbnation.com/artist/
|
58
|
-
lStatsTableNode = Nokogiri::HTML(lStatsTable.content[
|
59
|
+
lStatsTable = iMechanizeAgent.get("http://www.reverbnation.com/artist/new_stats_song_plays_table/#{iUserID}?all_time=true")
|
60
|
+
lStatsTableNode = Nokogiri::HTML(lStatsTable.content[36..-4].gsub(/\\"/,'"').gsub(/\\n/,"\n").gsub(/\\r/,''))
|
59
61
|
# Screen scrap it
|
60
62
|
lLstSongsRead = []
|
61
63
|
lStatsTableNode.css('table.statstable_full tr')[1..-3].each do |iSongNode|
|
@@ -63,14 +65,12 @@ module StatsCollect
|
|
63
65
|
lSongTitle = lNodeContents[1].content
|
64
66
|
lNbrSongPlays = Integer(lNodeContents[3].content)
|
65
67
|
lNbrSongDownloads = Integer(lNodeContents[4].content)
|
66
|
-
|
67
|
-
|
68
|
-
lMatch = lNodeContents[7].content.match(/^(\d*)\/(\d*)$/)
|
68
|
+
lPlayRatio = Integer(lNodeContents[5].content.match(/^(\d*)%$/)[1])
|
69
|
+
lMatch = lNodeContents[6].content.match(/^(\d*)\/(\d*)$/)
|
69
70
|
lNbrLikes = Integer(lMatch[1])
|
70
71
|
lNbrDislikes = Integer(lMatch[2])
|
71
72
|
oStatsProxy.addStat(lSongTitle, 'Song plays', lNbrSongPlays)
|
72
73
|
oStatsProxy.addStat(lSongTitle, 'Song downloads', lNbrSongDownloads)
|
73
|
-
oStatsProxy.addStat(lSongTitle, 'Video plays', lNbrVideoPlays)
|
74
74
|
oStatsProxy.addStat(lSongTitle, 'Song play ratio', lPlayRatio)
|
75
75
|
oStatsProxy.addStat(lSongTitle, 'Song likes', lNbrLikes)
|
76
76
|
oStatsProxy.addStat(lSongTitle, 'Song dislikes', lNbrDislikes)
|
@@ -79,6 +79,28 @@ module StatsCollect
|
|
79
79
|
logDebug "#{lLstSongsRead.size} songs read: #{lLstSongsRead.join(', ')}"
|
80
80
|
end
|
81
81
|
|
82
|
+
# Get the videos statistics
|
83
|
+
#
|
84
|
+
# Parameters:
|
85
|
+
# * *oStatsProxy* (_StatsProxy_): The stats proxy to be used to populate stats
|
86
|
+
# * *iMechanizeAgent* (_Mechanize_): The agent reading pages
|
87
|
+
# * *iUserID* (_String_): The ReverbNation user ID
|
88
|
+
def getVideos(oStatsProxy, iMechanizeAgent, iUserID)
|
89
|
+
# Get the Ajax stats table
|
90
|
+
lStatsTable = iMechanizeAgent.get("http://www.reverbnation.com/artist/new_stats_video_plays_table/#{iUserID}?all_time=true")
|
91
|
+
lStatsTableNode = Nokogiri::HTML(lStatsTable.content[37..-4].gsub(/\\"/,'"').gsub(/\\n/,"\n").gsub(/\\r/,''))
|
92
|
+
# Screen scrap it
|
93
|
+
lLstVideosRead = []
|
94
|
+
lStatsTableNode.css('table.statstable_full tr')[1..-3].each do |iSongNode|
|
95
|
+
lNodeContents = iSongNode.css('td')
|
96
|
+
lVideoTitle = lNodeContents[1].children[0].content
|
97
|
+
lNbrVideoPlays = Integer(lNodeContents[3].content)
|
98
|
+
oStatsProxy.addStat(lVideoTitle, 'Video plays', lNbrVideoPlays)
|
99
|
+
lLstVideosRead << lVideoTitle
|
100
|
+
end
|
101
|
+
logDebug "#{lLstVideosRead.size} videos read: #{lLstVideosRead.join(', ')}"
|
102
|
+
end
|
103
|
+
|
82
104
|
# Get the report statistics
|
83
105
|
#
|
84
106
|
# Parameters:
|
@@ -94,9 +116,9 @@ module StatsCollect
|
|
94
116
|
lChildrenNodes = iStatsSectionNode.children
|
95
117
|
lChildrenNodes.each_with_index do |iNode, iIdx|
|
96
118
|
if (iNode.content == 'Genre:')
|
97
|
-
lChartPositionGenre = Integer(lChildrenNodes[iIdx+1].content.strip)
|
119
|
+
lChartPositionGenre = Integer(lChildrenNodes[iIdx+1].content.strip.gsub(',',''))
|
98
120
|
elsif (iNode.content == 'Global:')
|
99
|
-
lChartPositionGlobal = Integer(lChildrenNodes[iIdx+1].content.strip)
|
121
|
+
lChartPositionGlobal = Integer(lChildrenNodes[iIdx+1].content.strip.gsub(',',''))
|
100
122
|
end
|
101
123
|
end
|
102
124
|
if ((lChartPositionGenre != nil) and
|
@@ -113,9 +135,9 @@ module StatsCollect
|
|
113
135
|
lChildrenNodes = iPaneNode.children
|
114
136
|
lChildrenNodes.each_with_index do |iNode, iIdx|
|
115
137
|
if (iNode.content == 'Band Equity Score: ')
|
116
|
-
lBandEquityScore = Integer(lChildrenNodes[iIdx+1].content.strip)
|
138
|
+
lBandEquityScore = Integer(lChildrenNodes[iIdx+1].content.strip.gsub(',',''))
|
117
139
|
elsif (iNode.content == 'Total Fans: ')
|
118
|
-
lNbrFriends = Integer(lChildrenNodes[iIdx+1].content.strip)
|
140
|
+
lNbrFriends = Integer(lChildrenNodes[iIdx+1].content.strip.gsub(',',''))
|
119
141
|
end
|
120
142
|
end
|
121
143
|
if ((lBandEquityScore != nil) and
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c)
|
2
|
+
# Copyright (c) 2010 - 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
3
|
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
4
|
#++
|
5
5
|
|
@@ -22,10 +22,14 @@ module StatsCollect
|
|
22
22
|
def execute(oStatsProxy, iConf, iLstObjects, iLstCategories)
|
23
23
|
require 'mechanize'
|
24
24
|
lMechanizeAgent = Mechanize.new
|
25
|
-
lLoginForm = lMechanizeAgent.get('http://www.youtube.com').link_with(:text => 'Sign In').click.forms[
|
25
|
+
lLoginForm = lMechanizeAgent.get('http://www.youtube.com').link_with(:text => 'Sign In').click.forms[0]
|
26
26
|
lLoginForm.Email = iConf[:LoginEMail]
|
27
27
|
lLoginForm.Passwd = iConf[:LoginPassword]
|
28
|
-
|
28
|
+
if (Mechanize::VERSION > '1.0.0')
|
29
|
+
lMechanizeAgent.submit(lLoginForm, lLoginForm.buttons.first).meta_refresh.first.click
|
30
|
+
else
|
31
|
+
lMechanizeAgent.submit(lLoginForm, lLoginForm.buttons.first).meta.first.click
|
32
|
+
end
|
29
33
|
if ((oStatsProxy.isCategoryIncluded?('Video plays')) or
|
30
34
|
(oStatsProxy.isCategoryIncluded?('Video likes')) or
|
31
35
|
(oStatsProxy.isCategoryIncluded?('Video dislikes')) or
|
@@ -84,13 +88,13 @@ module StatsCollect
|
|
84
88
|
lOverviewPage = iMechanizeAgent.get('http://www.youtube.com/account_overview')
|
85
89
|
lNbrVisits = nil
|
86
90
|
lNbrFollowers = nil
|
87
|
-
lOverviewPage.root.css('div.statBlock').each do |iStatsSectionNode|
|
91
|
+
lOverviewPage.root.css('div.statBlock div').each do |iStatsSectionNode|
|
88
92
|
lChildrenNodes = iStatsSectionNode.children
|
89
93
|
lChildrenNodes.each_with_index do |iNode, iIdx|
|
90
94
|
if (iNode.content == 'Channel Views:')
|
91
|
-
lNbrVisits = Integer(lChildrenNodes[iIdx+1].content.strip)
|
95
|
+
lNbrVisits = Integer(lChildrenNodes[iIdx+1].content.strip.gsub(/,/,''))
|
92
96
|
elsif (iNode.content == 'Subscribers:')
|
93
|
-
lNbrFollowers = Integer(lChildrenNodes[iIdx+1].content.strip)
|
97
|
+
lNbrFollowers = Integer(lChildrenNodes[iIdx+1].content.strip.gsub(/,/,''))
|
94
98
|
end
|
95
99
|
end
|
96
100
|
if ((lNbrVisits != nil) and
|
@@ -99,9 +103,9 @@ module StatsCollect
|
|
99
103
|
end
|
100
104
|
end
|
101
105
|
if (lNbrVisits == nil)
|
102
|
-
logErr "Unable to get number of visits: #{lOverviewPage}"
|
106
|
+
logErr "Unable to get number of visits: #{lOverviewPage.content}"
|
103
107
|
elsif (lNbrFollowers == nil)
|
104
|
-
logErr "Unable to get number of followers: #{lOverviewPage}"
|
108
|
+
logErr "Unable to get number of followers: #{lOverviewPage.content}"
|
105
109
|
else
|
106
110
|
oStatsProxy.addStat('Global', 'Visits', lNbrVisits)
|
107
111
|
oStatsProxy.addStat('Global', 'Followers', lNbrFollowers)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2010 - 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
|
+
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
|
+
#++
|
5
|
+
|
6
|
+
module StatsCollect
|
7
|
+
|
8
|
+
module Notifiers
|
9
|
+
|
10
|
+
class Custom
|
11
|
+
|
12
|
+
# Send a given notification
|
13
|
+
#
|
14
|
+
# Parameters:
|
15
|
+
# * *iConf* (<em>map<Symbol,Object></em>): The notifier config
|
16
|
+
# * *iMessage* (_String_): Message to send
|
17
|
+
def sendNotification(iConf, iMessage)
|
18
|
+
iConf[:SendCode].call(iMessage)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2010 - 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
|
+
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
|
+
#++
|
5
|
+
|
6
|
+
module StatsCollect
|
7
|
+
|
8
|
+
module Notifiers
|
9
|
+
|
10
|
+
class LogFile
|
11
|
+
|
12
|
+
# Send a given notification
|
13
|
+
#
|
14
|
+
# Parameters:
|
15
|
+
# * *iConf* (<em>map<Symbol,Object></em>): The notifier config
|
16
|
+
# * *iMessage* (_String_): Message to send
|
17
|
+
def sendNotification(iConf, iMessage)
|
18
|
+
File.open(iConf[:LogFile], (iConf[:Append] == true) ? 'a' : 'w') do |oFile|
|
19
|
+
oFile.write(iMessage)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/lib/StatsCollect/Stats.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c)
|
2
|
+
# Copyright (c) 2010 - 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
3
|
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
4
|
#++
|
5
5
|
|
6
6
|
require 'date'
|
7
7
|
require 'optparse'
|
8
8
|
require 'StatsCollect/StatsProxy'
|
9
|
+
require 'StatsCollect/StatsOrdersProxy'
|
9
10
|
|
10
11
|
module StatsCollect
|
11
12
|
|
@@ -18,6 +19,8 @@ module StatsCollect
|
|
18
19
|
STATS_VALUE_TYPE_FLOAT = 1
|
19
20
|
STATS_VALUE_TYPE_PERCENTAGE = 2
|
20
21
|
STATS_VALUE_TYPE_UNKNOWN = 3
|
22
|
+
STATS_VALUE_TYPE_MAP = 4
|
23
|
+
STATS_VALUE_TYPE_STRING = 5
|
21
24
|
|
22
25
|
class Stats
|
23
26
|
|
@@ -154,77 +157,73 @@ module StatsCollect
|
|
154
157
|
def collect
|
155
158
|
rErrorCode = 0
|
156
159
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
if (File.exists?(lLockFile))
|
161
|
-
logErr "Another instance of stats collection is already running. Delete file #{lLockFile} if it is not."
|
162
|
-
begin
|
163
|
-
lDetails = nil
|
164
|
-
File.open(lLockFile, 'r') do |iFile|
|
165
|
-
lDetails = eval(iFile.read)
|
166
|
-
end
|
167
|
-
logErr "Details of the running instance: #{lDetails.inspect}"
|
168
|
-
rErrorCode = 12
|
169
|
-
rescue Exception
|
170
|
-
logErr "Invalid lock file #{lLockFile}: #{$!}."
|
171
|
-
rErrorCode = 13
|
172
|
-
end
|
173
|
-
else
|
174
|
-
File.open(lLockFile, 'w') do |oFile|
|
175
|
-
oFile << "
|
176
|
-
{
|
177
|
-
:ExecutionTime => '#{DateTime.now.strftime('%Y-%m-%d %H:%M:%S')}',
|
178
|
-
:PID => '#{Process.pid}'
|
179
|
-
}
|
180
|
-
"
|
181
|
-
end
|
160
|
+
require 'rUtilAnts/Misc'
|
161
|
+
RUtilAnts::Misc::initializeMisc
|
162
|
+
lMutexErrorCode = fileMutex('StatsCollect') do
|
182
163
|
begin
|
164
|
+
# The list of errors
|
165
|
+
lLstErrors = []
|
166
|
+
setLogErrorsStack(lLstErrors)
|
183
167
|
# Collect statistics
|
184
|
-
logInfo "[#{DateTime.now.strftime('%Y-%m-%d %H:%M:%S')}] - Begin collecting stats..."
|
185
|
-
if (!@BackendInit)
|
186
|
-
@BackendInstance.initSession(@Conf[:Backends][@Backend])
|
187
|
-
@BackendInit = true
|
188
|
-
end
|
189
|
-
# Get the stats orders to process
|
168
|
+
logInfo "[#{DateTime.now.strftime('%Y-%m-%d %H:%M:%S')}] - Begin collecting stats (PID #{Process.pid})..."
|
190
169
|
lFoundOrder = false
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
170
|
+
lNbrErrors = 0
|
171
|
+
setupBackend do
|
172
|
+
# Get the stats orders to process
|
173
|
+
lStatsOrdersProxy = StatsOrdersProxy.new
|
174
|
+
@BackendInstance.getStatsOrders(lStatsOrdersProxy)
|
175
|
+
lFoundOrder = (!lStatsOrdersProxy.StatsOrders.empty?)
|
176
|
+
# Process each stats order
|
177
|
+
lStatsOrdersProxy.StatsOrders.each do |iLstIDs, iStatsOrderInfo|
|
178
|
+
iTimeStamp, iLstLocations, iLstObjects, iLstCategories, iStatus = iStatsOrderInfo
|
179
|
+
@BackendInstance.dequeueStatsOrders(iLstIDs)
|
180
|
+
logInfo "Dequeued stats order: IDs: #{iLstIDs.join('|')}, Time: #{iTimeStamp}, Locations: #{iLstLocations.join('|')}, Objects: #{iLstObjects.join('|')}, Categories: #{iLstCategories.join('|')}, Status: #{iStatus}"
|
181
|
+
begin
|
182
|
+
lRecoverableOrders, lUnrecoverableOrders = processOrder(iLstObjects, iLstCategories, iLstLocations)
|
183
|
+
# Add recoverable orders back
|
184
|
+
lRecoverableOrders.each do |iOrderInfo|
|
185
|
+
lNbrErrors += 1
|
186
|
+
iLstRecoverableObjects, iLstRecoverableCategories, iLstRecoverableLocations = iOrderInfo
|
187
|
+
logInfo "Enqueue recoverable order: Locations: #{iLstRecoverableLocations.join('|')}, Objects: #{iLstRecoverableObjects.join('|')}, Categories: #{iLstRecoverableCategories.join('|')}"
|
188
|
+
@BackendInstance.putNewStatsOrder(DateTime.now + @Conf[:RecoverableErrorsRetryDelay]/86400.0, iLstRecoverableLocations, iLstRecoverableObjects, iLstRecoverableCategories, STATS_ORDER_STATUS_RECOVERABLE_ERROR)
|
189
|
+
end
|
190
|
+
# Add unrecoverable orders back
|
191
|
+
lUnrecoverableOrders.each do |iOrderInfo|
|
192
|
+
lNbrErrors += 1
|
193
|
+
iLstUnrecoverableObjects, iLstUnrecoverableCategories, iLstUnrecoverableLocations = iOrderInfo
|
194
|
+
logInfo "Enqueue unrecoverable order: Locations: #{iLstUnrecoverableLocations.join('|')}, Objects: #{iLstUnrecoverableObjects.join('|')}, Categories: #{iLstUnrecoverableCategories.join('|')}"
|
195
|
+
@BackendInstance.putNewStatsOrder(iTimeStamp, iLstUnrecoverableLocations, iLstUnrecoverableObjects, iLstUnrecoverableCategories, STATS_ORDER_STATUS_UNRECOVERABLE_ERROR)
|
196
|
+
end
|
197
|
+
@BackendInstance.commit
|
198
|
+
rescue Exception
|
199
|
+
lNbrErrors += 1
|
200
|
+
@BackendInstance.rollback
|
201
|
+
logErr "Exception while processing order #{iTimeStamp}, Locations: #{iLstLocations.join('|')}, Objects: #{iLstObjects.join('|')}, Categories: #{iLstCategories.join('|')}, Status: #{iStatus}: #{$!}.\n#{$!.backtrace.join("\n")}\n"
|
202
|
+
rErrorCode = 14
|
208
203
|
end
|
209
|
-
@BackendInstance.commit
|
210
|
-
rescue Exception
|
211
|
-
@BackendInstance.rollback
|
212
|
-
logErr "Exception while processing order #{lTimeStamp}, Locations: #{lLstLocations.join('|')}, Objects: #{lLstObjects.join('|')}, Categories: #{lLstCategories.join('|')}, Status: #{lStatus}: #{$!}.\n#{$!.backtrace.join("\n")}\n"
|
213
|
-
rErrorCode = 14
|
214
204
|
end
|
215
|
-
lTimeStamp, lLstLocations, lLstObjects, lLstCategories, lStatus = @BackendInstance.getNextStatsOrder
|
216
205
|
end
|
217
206
|
if (!lFoundOrder)
|
218
207
|
@NotifyUser = false
|
219
208
|
end
|
209
|
+
setLogErrorsStack(nil)
|
210
|
+
if (lNbrErrors > 0)
|
211
|
+
logErr "#{lNbrErrors} orders were put in error during processing. Please check logs."
|
212
|
+
end
|
213
|
+
if (!lLstErrors.empty?)
|
214
|
+
logErr "#{lLstErrors.size} errors were reported. Check log for exact errors."
|
215
|
+
end
|
220
216
|
logInfo "[#{DateTime.now.strftime('%Y-%m-%d %H:%M:%S')}] - Stats collection finished."
|
221
|
-
File.unlink(lLockFile)
|
222
217
|
rescue Exception
|
223
218
|
logErr "Exception thrown while collecting stats: #{$!}.\n#{$!.backtrace.join("\n")}"
|
224
219
|
rErrorCode = 15
|
225
220
|
@NotifyUser = true
|
226
221
|
end
|
227
222
|
end
|
223
|
+
if ((lMutexErrorCode != RUtilAnts::Misc::FILEMUTEX_NO_LOCK) and
|
224
|
+
(lMutexErrorCode != RUtilAnts::Misc::FILEMUTEX_ZOMBIE_LOCK))
|
225
|
+
rErrorCode = 12
|
226
|
+
end
|
228
227
|
|
229
228
|
return rErrorCode
|
230
229
|
end
|
@@ -254,15 +253,32 @@ module StatsCollect
|
|
254
253
|
# * *iLstObjects* (<em>list<String></em>): Objects list (can be empty for all objects)
|
255
254
|
# * *iLstCategories* (<em>list<String></em>): Categories list (can be empty for all categories)
|
256
255
|
def pushStatsOrder(iLstLocations, iLstObjects, iLstCategories)
|
256
|
+
setupBackend do
|
257
|
+
@BackendInstance.putNewStatsOrder(DateTime.now, iLstLocations, iLstObjects, iLstCategories, STATS_ORDER_STATUS_TOBEPROCESSED)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
private
|
262
|
+
|
263
|
+
# Call some code initializing backend before and ensuring it will be closed after.
|
264
|
+
# This method is re-entrant.
|
265
|
+
#
|
266
|
+
# Parameters:
|
267
|
+
# * _CodeBlock_: the code to be called
|
268
|
+
def setupBackend
|
269
|
+
lBackendInitHere = false
|
257
270
|
if (!@BackendInit)
|
258
271
|
@BackendInstance.initSession(@Conf[:Backends][@Backend])
|
259
272
|
@BackendInit = true
|
273
|
+
lBackendInitHere = true
|
274
|
+
end
|
275
|
+
yield
|
276
|
+
if (lBackendInitHere)
|
277
|
+
@BackendInstance.closeSession
|
278
|
+
@BackendInit = false
|
260
279
|
end
|
261
|
-
@BackendInstance.putNewStatsOrder(DateTime.now, iLstLocations, iLstObjects, iLstCategories, STATS_ORDER_STATUS_TOBEPROCESSED)
|
262
280
|
end
|
263
281
|
|
264
|
-
private
|
265
|
-
|
266
282
|
# Process an order
|
267
283
|
#
|
268
284
|
# Parameters:
|
@@ -292,7 +308,7 @@ module StatsCollect
|
|
292
308
|
lPlugin, lError = getPluginInstance('Locations', iPluginName)
|
293
309
|
if (lError == nil)
|
294
310
|
# Ask the plugin to perform the order
|
295
|
-
lStatsProxy = StatsProxy.new(iObjectsList, iCategoriesList)
|
311
|
+
lStatsProxy = StatsProxy.new(iObjectsList, iCategoriesList, @BackendInstance, iPluginName)
|
296
312
|
logInfo "===== Call Location plugin #{iPluginName} to perform order..."
|
297
313
|
begin
|
298
314
|
lPlugin.execute(lStatsProxy, lPluginConf, iObjectsList, iCategoriesList)
|
@@ -303,7 +319,7 @@ module StatsCollect
|
|
303
319
|
end
|
304
320
|
if (lError == nil)
|
305
321
|
# Write the stats into the database
|
306
|
-
writeStats(lStatsProxy.StatsToAdd,
|
322
|
+
writeStats(lStatsProxy.StatsToAdd, iObjectsList, iCategoriesList)
|
307
323
|
# If the plugin failed on recoverable errors, note them
|
308
324
|
lStatsProxy.RecoverableOrders.each do |iOrderInfo|
|
309
325
|
iLstObjects, iLstCategories = iOrderInfo
|
@@ -384,10 +400,9 @@ module StatsCollect
|
|
384
400
|
#
|
385
401
|
# Parameters:
|
386
402
|
# * *iStatsToAdd* (<em>list<[TimeStamp,Object,Category,Value]></em>): The stats to write in the DB
|
387
|
-
# * *iLocation* (_String_): The location of these stats
|
388
403
|
# * *iLstObjects* (<em>list<String></em>): The filtering objects to write (can be empty for all)
|
389
404
|
# * *iLstCategories* (<em>list<String></em>): The filtering categories to write (can be empty for all)
|
390
|
-
def writeStats(iStatsToAdd,
|
405
|
+
def writeStats(iStatsToAdd, iLstObjects, iLstCategories)
|
391
406
|
# Filter the stats we will really add
|
392
407
|
lStatsToBeCommitted = nil
|
393
408
|
if ((iLstObjects.empty?) and
|
@@ -418,21 +433,33 @@ module StatsCollect
|
|
418
433
|
else
|
419
434
|
# Get the current locations from the DB to know if our location exists
|
420
435
|
lKnownLocations = @BackendInstance.getKnownLocations
|
421
|
-
lLocationID = lKnownLocations[iLocation]
|
422
|
-
if (lLocationID == nil)
|
423
|
-
# First create the new location and get its ID
|
424
|
-
logInfo "Creating new location: #{iLocation}"
|
425
|
-
lLocationID = @BackendInstance.addLocation(iLocation)
|
426
|
-
end
|
427
|
-
logDebug "Location used for those stats: #{iLocation} (#{lLocationID})"
|
428
436
|
# Get the list of categories, sorted by category name
|
429
437
|
lKnownCategories = @BackendInstance.getKnownCategories
|
430
438
|
# Get the list of objects, sorted by object name
|
431
439
|
# This map will eventually be completed if new objects are found among the stats to write.
|
432
440
|
lKnownObjects = @BackendInstance.getKnownObjects
|
441
|
+
# Use the following to generate a RB file that can be used with RB plugin.
|
442
|
+
if false
|
443
|
+
lStrStats = []
|
444
|
+
lStatsToBeCommitted.each do |iStatsInfo|
|
445
|
+
lCheckExistence, iTimeStamp, iLocation, iObject, iCategory, iValue = iStatsInfo
|
446
|
+
lStrStats << [ lCheckExistence, iTimeStamp.strftime('%Y-%m-%d %H:%M:%S'), iLocation, iObject, iCategory, iValue ].inspect
|
447
|
+
end
|
448
|
+
File.open('__StatsToBeWritten.rb', 'w') do |oFile|
|
449
|
+
oFile.write("[\n#{lStrStats.join(",\n")}\n]")
|
450
|
+
end
|
451
|
+
end
|
433
452
|
# Add statistics
|
434
453
|
lStatsToBeCommitted.each do |iStatsInfo|
|
435
|
-
iTimeStamp, iObject, iCategory, iValue = iStatsInfo
|
454
|
+
lCheckExistence, iTimeStamp, iLocation, iObject, iCategory, iValue = iStatsInfo
|
455
|
+
lLocationID = lKnownLocations[iLocation]
|
456
|
+
if (lLocationID == nil)
|
457
|
+
# First create the new location and get its ID
|
458
|
+
logInfo "Creating new location: #{iLocation}"
|
459
|
+
lLocationID = @BackendInstance.addLocation(iLocation)
|
460
|
+
lKnownLocations[iLocation] = lLocationID
|
461
|
+
lCheckExistence = false
|
462
|
+
end
|
436
463
|
# Check that the category exists
|
437
464
|
lValueType = nil
|
438
465
|
lCategoryID = nil
|
@@ -441,6 +468,7 @@ module StatsCollect
|
|
441
468
|
lValueType = STATS_VALUE_TYPE_UNKNOWN
|
442
469
|
lCategoryID = @BackendInstance.addCategory(iCategory, lValueType)
|
443
470
|
lKnownCategories[iCategory] = [ lCategoryID, lValueType ]
|
471
|
+
lCheckExistence = false
|
444
472
|
else
|
445
473
|
lCategoryID, lValueType = lKnownCategories[iCategory]
|
446
474
|
end
|
@@ -450,11 +478,24 @@ module StatsCollect
|
|
450
478
|
logInfo "Creating new object: #{iObject}"
|
451
479
|
lObjectID = @BackendInstance.addObject(iObject)
|
452
480
|
lKnownObjects[iObject] = lObjectID
|
481
|
+
lCheckExistence = false
|
482
|
+
end
|
483
|
+
# First, we ensure that this stats does not exist if we don't want duplicates
|
484
|
+
lAdd = true
|
485
|
+
if (lCheckExistence)
|
486
|
+
lExistingValue = @BackendInstance.getStat(iTimeStamp, lLocationID, lObjectID, lCategoryID, lValueType)
|
487
|
+
if (lExistingValue != nil)
|
488
|
+
logWarn "Stat value for #{iTimeStamp.strftime('%Y-%m-%d %H:%M:%S')}, Location: #{lLocationID}, Object: #{lObjectID}, Category: #{lCategoryID} already exists with value #{lExistingValue}. Will not duplicate it."
|
489
|
+
lAdd = false
|
490
|
+
end
|
491
|
+
end
|
492
|
+
if (lAdd)
|
493
|
+
# Add the stat
|
494
|
+
@BackendInstance.addStat(iTimeStamp, lLocationID, lObjectID, lCategoryID, iValue, lValueType)
|
495
|
+
logDebug "Added stat: Time: #{iTimeStamp}, Location: #{iLocation} (#{lLocationID}), Object: #{iObject} (#{lObjectID}), Category: #{iCategory} (#{lCategoryID}), Value: #{iValue}"
|
453
496
|
end
|
454
|
-
# Add the stat
|
455
|
-
@BackendInstance.addStat(iTimeStamp, lLocationID, lObjectID, lCategoryID, iValue, lValueType)
|
456
|
-
logDebug "Added stat: Time: #{iTimeStamp}, Location: #{iLocation} (#{lLocationID}), Object: #{iObject} (#{lObjectID}), Category: #{iCategory} (#{lCategoryID}), Value: #{iValue}"
|
457
497
|
end
|
498
|
+
logInfo "#{lStatsToBeCommitted.size} stats added."
|
458
499
|
end
|
459
500
|
end
|
460
501
|
|
@@ -0,0 +1,54 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2010 - 2011 Muriel Salvan (murielsalvan@users.sourceforge.net)
|
3
|
+
# Licensed under the terms specified in LICENSE file. No warranty is provided.
|
4
|
+
#++
|
5
|
+
|
6
|
+
module StatsCollect
|
7
|
+
|
8
|
+
# The stats orders proxy, used by plugins to populate stats
|
9
|
+
class StatsOrdersProxy
|
10
|
+
|
11
|
+
# List of categories among the stats orders stats orders, by IDs
|
12
|
+
# map< list< Integer >, [ DateTime, list<String>, list<String>, list<String>, Integer ] >
|
13
|
+
attr_reader :StatsOrders
|
14
|
+
|
15
|
+
# Constructor
|
16
|
+
def initialize
|
17
|
+
# List of stats orders IDs
|
18
|
+
@StatsOrders = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
# Add a new stats order
|
22
|
+
#
|
23
|
+
# Parameters:
|
24
|
+
# * *iID* (_Integer_): An ID identifying uniquely this stats order. It will be used to tell which stats orders have to be dequeued later.
|
25
|
+
# * *iTimeStamp* (_DateTime_): Time stamp
|
26
|
+
# * *iLstLocations* (<em>list<String></em>): List of locations
|
27
|
+
# * *iLstObjects* (<em>list<String></em>): List of objects
|
28
|
+
# * *iLstCategories* (<em>list<String></em>): List of categories
|
29
|
+
# * *iStatus* (_Integer_): Status
|
30
|
+
def addStatsOrder(iID, iTimeStamp, iLstLocations, iLstObjects, iLstCategories, iStatus)
|
31
|
+
lLstSortedLocations = iLstLocations.sort.uniq
|
32
|
+
lLstSortedObjects = iLstObjects.sort.uniq
|
33
|
+
lLstSortedCategories = iLstCategories.sort.uniq
|
34
|
+
# First, check if this stats order is not already present
|
35
|
+
lFound = false
|
36
|
+
@StatsOrders.each do |iIDs, iStatsOrderInfo|
|
37
|
+
iExistingTimeStamp, iLstExistingLocations, iLstExistingObjects, iLstExistingCategories, iExistingStatus = iStatsOrderInfo
|
38
|
+
if ((iLstExistingLocations == lLstSortedLocations) and
|
39
|
+
(iLstExistingObjects == lLstSortedObjects) and
|
40
|
+
(iLstExistingCategories == lLstSortedCategories))
|
41
|
+
# Found already here
|
42
|
+
iIDs << iID
|
43
|
+
lFound = true
|
44
|
+
break
|
45
|
+
end
|
46
|
+
end
|
47
|
+
if (!lFound)
|
48
|
+
@StatsOrders[[iID]] = [ iTimeStamp, lLstSortedLocations, lLstSortedObjects, lLstSortedCategories, iStatus ]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|