0
|
1 package nabble.model;
|
|
2
|
|
3 import fschmidt.db.DbDatabase;
|
|
4 import fschmidt.db.postgres.PostgresExceptionHandler;
|
|
5 import org.slf4j.Logger;
|
|
6 import org.slf4j.LoggerFactory;
|
|
7
|
|
8 import java.io.IOException;
|
|
9 import java.sql.Connection;
|
|
10 import java.sql.PreparedStatement;
|
|
11 import java.sql.ResultSet;
|
|
12 import java.sql.SQLException;
|
|
13 import java.sql.Statement;
|
|
14 import java.util.ArrayList;
|
|
15 import java.util.Collection;
|
|
16 import java.util.Collections;
|
|
17 import java.util.HashMap;
|
|
18 import java.util.Iterator;
|
|
19 import java.util.List;
|
|
20 import java.util.Map;
|
|
21 import java.util.concurrent.TimeUnit;
|
|
22
|
|
23
|
|
24 public final class ViewCount {
|
|
25 private static final Logger logger = LoggerFactory.getLogger(ViewCount.class);
|
|
26
|
|
27
|
|
28 // the ViewCount class is views per site
|
|
29 private Map<Long,int[]> nodeMap = new HashMap<Long,int[]>();
|
|
30 private int userViews = 0;
|
|
31
|
|
32 private ViewCount() {}
|
|
33
|
|
34 private void inc(long nodeId,boolean isUser) {
|
|
35 int[] i = nodeMap.get(nodeId);
|
|
36 if( i == null ) {
|
|
37 i = new int[1];
|
|
38 nodeMap.put(nodeId,i);
|
|
39 }
|
|
40 i[0]++;
|
|
41 if( isUser )
|
|
42 userViews++;
|
|
43 }
|
|
44
|
|
45
|
|
46 public static long lastSaved = System.currentTimeMillis();
|
|
47 private static final Object lock = new Object();
|
|
48 private static Map<Long,ViewCount> counts = new HashMap<Long,ViewCount>();
|
|
49
|
|
50 public static void inc(Site site,long nodeId,boolean isUser) {
|
|
51 Long siteId = site.getId();
|
|
52 synchronized(lock) {
|
|
53 ViewCount viewCount = counts.get(siteId);
|
|
54 if( viewCount == null ) {
|
|
55 viewCount = new ViewCount();
|
|
56 counts.put(siteId,viewCount);
|
|
57 }
|
|
58 viewCount.inc(nodeId,isUser);
|
|
59 }
|
|
60 }
|
|
61
|
|
62 public static void save() {
|
|
63 Map<Long,ViewCount> map;
|
|
64 synchronized(lock) {
|
|
65 map = counts;
|
|
66 counts = new HashMap<Long,ViewCount>();
|
|
67 }
|
|
68 Db.dbGlobal().beginTransaction();
|
|
69 try {
|
|
70 for( Map.Entry<Long,ViewCount> entry : map.entrySet() ) {
|
|
71 long siteId = entry.getKey();
|
|
72 ViewCount viewCount = entry.getValue();
|
|
73 Map<Long,int[]> nodeMap =viewCount.nodeMap;
|
|
74 SiteKey siteKey = SiteKey.getInstance(siteId);
|
|
75 DbDatabase db;
|
|
76 try {
|
|
77 db = siteKey.getDb();
|
|
78 } catch(UpdatingException e) {
|
|
79 continue;
|
|
80 }
|
|
81 int siteViews = 0;
|
|
82 {
|
|
83 Connection con = db.getConnection();
|
|
84 try {
|
|
85 PreparedStatement pstmtUpdate = con.prepareStatement(
|
|
86 "update view_count set views = views + ? where node_id = ?"
|
|
87 );
|
|
88 PreparedStatement pstmtInsert = null;
|
|
89 for( Map.Entry<Long,int[]> nodeEntry : nodeMap.entrySet() ) {
|
|
90 long nodeId = nodeEntry.getKey();
|
|
91 int views = nodeEntry.getValue()[0];
|
|
92 siteViews += views;
|
|
93 {
|
|
94 pstmtUpdate.setInt(1,views);
|
|
95 pstmtUpdate.setLong(2,nodeId);
|
|
96 PostgresExceptionHandler peh = new PostgresExceptionHandler(pstmtUpdate);
|
|
97 try {
|
|
98 int n = pstmtUpdate.executeUpdate();
|
|
99 if( n==0 ) {
|
|
100 if( pstmtInsert == null ) {
|
|
101 pstmtInsert = con.prepareStatement(
|
|
102 "insert into view_count (node_id,views) values (?,?)"
|
|
103 );
|
|
104 }
|
|
105 pstmtInsert.setLong(1,nodeId);
|
|
106 pstmtInsert.setInt(2,views);
|
|
107 pstmtInsert.executeUpdate();
|
|
108 }
|
|
109 } catch(SQLException e) {
|
|
110 if( e.getMessage().contains("violates foreign key constraint") ) {
|
|
111 logger.info("in site "+siteId+" node "+nodeId+" was viewed and then deleted");
|
|
112 peh.handleException();
|
|
113 } else if( e.getMessage().contains("relation \"view_count\" does not exist") ) {
|
|
114 logger.info("site "+siteId+" was deleted");
|
|
115 peh.handleException();
|
|
116 } else {
|
|
117 throw e;
|
|
118 }
|
|
119 } finally {
|
|
120 peh.close();
|
|
121 }
|
|
122 }
|
|
123 }
|
|
124 if( pstmtInsert != null )
|
|
125 pstmtInsert.close();
|
|
126 pstmtUpdate.close();
|
|
127 } finally {
|
|
128 con.close();
|
|
129 }
|
|
130 }
|
|
131 {
|
|
132 Connection con = Db.dbGlobal().getConnection();
|
|
133 PreparedStatement pstmt = con.prepareStatement(
|
|
134 "update site_global"
|
|
135 +" set views = views + ?"
|
|
136 +" , user_views = user_views + ?"
|
|
137 +" where site_id = ?"
|
|
138 );
|
|
139 pstmt.setInt(1,siteViews);
|
|
140 pstmt.setInt(2,viewCount.userViews);
|
|
141 pstmt.setLong(3,siteId);
|
|
142 pstmt.executeUpdate();
|
|
143 con.close();
|
|
144 }
|
|
145 }
|
|
146 Db.dbGlobal().commitTransaction();
|
|
147 lastSaved = System.currentTimeMillis();
|
|
148 } catch(SQLException e) {
|
|
149 logger.error("Views not saved", e);
|
|
150 } finally {
|
|
151 Db.dbGlobal().endTransaction();
|
|
152 }
|
|
153 }
|
|
154
|
|
155 private static final long secondsBetweenViewCountSaves = Init.get("secondsBetweenViewCountSaves",60);
|
|
156
|
|
157 static {
|
|
158 Executors.scheduleWithFixedDelay(new Runnable(){public void run(){
|
|
159 save();
|
|
160 }},secondsBetweenViewCountSaves,secondsBetweenViewCountSaves,TimeUnit.SECONDS);
|
|
161 logger.info("Started ViewCounter");
|
|
162
|
|
163 Executors.runDaily(
|
|
164 new Runnable(){public void run(){
|
|
165 try {
|
|
166 calculateActivity();
|
|
167 } catch(SQLException e) {
|
|
168 logger.error("",e);
|
|
169 } catch(IOException e) {
|
|
170 logger.error("",e);
|
|
171 }
|
|
172 }}
|
|
173 );
|
|
174 }
|
|
175
|
|
176 private static final float DECAY = Init.get("activityDecay",0.95f);
|
|
177 static final int initialActivity = Init.get("initialActivity",1000);
|
|
178 static final int updateChunks = Init.get("updateChunks",100);
|
|
179
|
|
180 public static final Map<Long,Integer> activityBoosts = Init.get("activityBoosts",new HashMap<Long,Integer>());
|
|
181
|
|
182 private static final List<Runnable> calculateActivityListeners = new ArrayList<Runnable>();
|
|
183
|
|
184 public static void addCalculateActivityListener(Runnable listener) {
|
|
185 synchronized(calculateActivityListeners) {
|
|
186 calculateActivityListeners.add(listener);
|
|
187 }
|
|
188 }
|
|
189
|
|
190 private static void fireCalculateActivityListeners() {
|
|
191 synchronized(calculateActivityListeners) {
|
|
192 for( Runnable listener : calculateActivityListeners ) {
|
|
193 listener.run();
|
|
194 }
|
|
195 }
|
|
196 }
|
|
197
|
|
198 public static void calculateActivity() throws SQLException, IOException {
|
|
199 logger.error("calculateActivity start");
|
|
200 Connection con = Db.dbGlobal().getConnection();
|
|
201 try {
|
|
202 {
|
|
203 Statement stmt = con.createStatement();
|
|
204
|
|
205 stmt.executeUpdate(
|
|
206 "update site_global"
|
|
207 +" set activity = " + initialActivity
|
|
208 +" where activity is null"
|
|
209 );
|
|
210
|
|
211 PreparedStatement pstmt = con.prepareStatement(
|
|
212 "update site_global"
|
|
213 +" set activity = floor(activity * ?)"
|
|
214 +" where activity > 0"
|
|
215 +" and site_id % ? = ?"
|
|
216 );
|
|
217 for( int chunk = 0; chunk < updateChunks && !Executors.isShuttingDown(); chunk++ ) {
|
|
218 long start = System.currentTimeMillis();
|
|
219 pstmt.setFloat(1,DECAY);
|
|
220 pstmt.setInt(2,updateChunks);
|
|
221 pstmt.setInt(3,chunk);
|
|
222 pstmt.executeUpdate();
|
|
223 logger.info("calculateActivity - chunk #"+chunk+" took " + (System.currentTimeMillis()-start)/1000 + " seconds");
|
|
224 }
|
|
225 pstmt.close();
|
|
226
|
|
227 fireCalculateActivityListeners();
|
|
228
|
|
229 stmt.executeUpdate(
|
|
230 "update site_global"
|
|
231 +" set activity = activity + views"
|
|
232 +" , views = 0"
|
|
233 +" , user_views = 0"
|
|
234 +" where views > 0"
|
|
235 );
|
|
236
|
|
237 stmt.close();
|
|
238 }
|
|
239 {
|
|
240 PreparedStatement stmt = con.prepareStatement(
|
|
241 "update site set activity = activity + ? where site_id = ?"
|
|
242 );
|
|
243 for( Map.Entry<Long,Integer> boost : activityBoosts.entrySet() ) {
|
|
244 stmt.setInt(1,boost.getValue());
|
|
245 stmt.setLong(2,boost.getKey());
|
|
246 stmt.executeQuery();
|
|
247 }
|
|
248 stmt.close();
|
|
249 }
|
|
250 } finally {
|
|
251 con.close();
|
|
252 }
|
|
253 logger.error("calculateActivity end");
|
|
254 }
|
|
255
|
|
256 public static Map<Long,Integer> getCounts(Site site,Collection<Long> nodeIds) {
|
|
257 if( nodeIds.isEmpty() )
|
|
258 return Collections.emptyMap();
|
|
259 Map<Long,Integer> map = new HashMap<Long,Integer>();
|
|
260 for( Long nodeId : nodeIds ) {
|
|
261 map.put(nodeId,0);
|
|
262 }
|
|
263 try {
|
|
264 Connection con = site.getDb().getConnection();
|
|
265 try {
|
|
266 StringBuilder buf = new StringBuilder();
|
|
267 buf.append( "select * from view_count where node_id in (" );
|
|
268 Iterator<Long> iter = nodeIds.iterator();
|
|
269 buf.append( iter.next() );
|
|
270 while( iter.hasNext() ) {
|
|
271 buf.append( ',' ).append( iter.next() );
|
|
272 }
|
|
273 buf.append(')');
|
|
274 Statement stmt = con.createStatement();
|
|
275 ResultSet rs = stmt.executeQuery( buf.toString() );
|
|
276 while( rs.next() ) {
|
|
277 map.put( rs.getLong("node_id"), rs.getInt("views") );
|
|
278 }
|
|
279 stmt.close();
|
|
280 } finally {
|
|
281 con.close();
|
|
282 }
|
|
283 } catch(SQLException e) {
|
|
284 throw new RuntimeException(e);
|
|
285 }
|
|
286 synchronized(lock) {
|
|
287 ViewCount viewCount = counts.get(site.getId());
|
|
288 if( viewCount != null ) {
|
|
289 for( Map.Entry<Long,Integer> entry : map.entrySet() ) {
|
|
290 Long nodeId = entry.getKey();
|
|
291 int[] views = viewCount.nodeMap.get(nodeId);
|
|
292 if( views != null )
|
|
293 map.put( nodeId, entry.getValue() + views[0] );
|
|
294 }
|
|
295 }
|
|
296 }
|
|
297 return map;
|
|
298 }
|
|
299 }
|