StatsCollect 0.1.1.20101220 → 0.2.0.20110830
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|