Mercurial Hosting > nabble
comparison src/nabble/model/ViewCount.java @ 0:7ecd1a4ef557
add content
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Thu, 21 Mar 2019 19:15:52 -0600 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:7ecd1a4ef557 |
---|---|
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 } |