001: /*******************************************************************************
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: *******************************************************************************/package org.ofbiz.webapp.stats;
019:
020: import java.net.InetAddress;
021: import java.util.Calendar;
022: import java.util.Date;
023: import java.util.HashMap;
024: import java.util.Iterator;
025: import java.util.LinkedList;
026: import java.util.Map;
027: import javax.servlet.http.HttpServletRequest;
028:
029: import org.ofbiz.base.util.Debug;
030: import org.ofbiz.base.util.UtilHttp;
031: import org.ofbiz.base.util.UtilMisc;
032: import org.ofbiz.base.util.UtilProperties;
033: import org.ofbiz.entity.GenericDelegator;
034: import org.ofbiz.entity.GenericEntityException;
035: import org.ofbiz.entity.GenericValue;
036: import org.ofbiz.entity.model.ModelEntity;
037:
038: /**
039: * <p>Counts server hits and tracks statistics for request, events and views
040: * <p>Handles total stats since the server started and binned
041: * stats according to settings in the serverstats.properties file.
042: */
043: public class ServerHitBin {
044: // Debug module name
045: public static final String module = ServerHitBin.class.getName();
046:
047: public static final int REQUEST = 1;
048: public static final int EVENT = 2;
049: public static final int VIEW = 3;
050: public static final int ENTITY = 4;
051: public static final int SERVICE = 5;
052: public static final String[] typeNames = { "", "Request", "Event",
053: "View", "Entity", "Service" };
054: public static final String[] typeIds = { "", "REQUEST", "EVENT",
055: "VIEW", "ENTITY", "SERVICE" };
056:
057: public static void countRequest(String id,
058: HttpServletRequest request, long startTime,
059: long runningTime, GenericValue userLogin,
060: GenericDelegator delegator) {
061: countHit(id, REQUEST, request, startTime, runningTime,
062: userLogin, delegator);
063: }
064:
065: public static void countEvent(String id,
066: HttpServletRequest request, long startTime,
067: long runningTime, GenericValue userLogin,
068: GenericDelegator delegator) {
069: countHit(id, EVENT, request, startTime, runningTime, userLogin,
070: delegator);
071: }
072:
073: public static void countView(String id, HttpServletRequest request,
074: long startTime, long runningTime, GenericValue userLogin,
075: GenericDelegator delegator) {
076: countHit(id, VIEW, request, startTime, runningTime, userLogin,
077: delegator);
078: }
079:
080: public static void countEntity(String id,
081: HttpServletRequest request, long startTime,
082: long runningTime, GenericValue userLogin,
083: GenericDelegator delegator) {
084: countHit(id, ENTITY, request, startTime, runningTime,
085: userLogin, delegator);
086: }
087:
088: public static void countService(String id,
089: HttpServletRequest request, long startTime,
090: long runningTime, GenericValue userLogin,
091: GenericDelegator delegator) {
092: countHit(id, SERVICE, request, startTime, runningTime,
093: userLogin, delegator);
094: }
095:
096: public static void countHit(String id, int type,
097: HttpServletRequest request, long startTime,
098: long runningTime, GenericValue userLogin,
099: GenericDelegator delegator) {
100: // only count hits if enabled, if not specified defaults to false
101: if (!"true".equals(UtilProperties.getPropertyValue(
102: "serverstats", "stats.enable." + typeIds[type])))
103: return;
104: countHit(id, type, request, startTime, runningTime, userLogin,
105: delegator, true);
106: }
107:
108: public static void advanceAllBins(long toTime) {
109: advanceAllBins(toTime, requestHistory);
110: advanceAllBins(toTime, eventHistory);
111: advanceAllBins(toTime, viewHistory);
112: advanceAllBins(toTime, entityHistory);
113: advanceAllBins(toTime, serviceHistory);
114: }
115:
116: static void advanceAllBins(long toTime, Map binMap) {
117: Iterator entries = binMap.entrySet().iterator();
118:
119: while (entries.hasNext()) {
120: Map.Entry entry = (Map.Entry) entries.next();
121:
122: if (entry.getValue() != null) {
123: ServerHitBin bin = (ServerHitBin) entry.getValue();
124:
125: bin.advanceBin(toTime);
126: }
127: }
128: }
129:
130: protected static void countHit(String id, int type,
131: HttpServletRequest request, long startTime,
132: long runningTime, GenericValue userLogin,
133: GenericDelegator delegator, boolean isOriginal) {
134: if (delegator == null) {
135: throw new IllegalArgumentException(
136: "The delgator passed to countHit cannot be null");
137: }
138:
139: ServerHitBin bin = null;
140: LinkedList binList = null;
141:
142: switch (type) {
143: case REQUEST:
144: binList = (LinkedList) requestHistory.get(id);
145: break;
146:
147: case EVENT:
148: binList = (LinkedList) eventHistory.get(id);
149: break;
150:
151: case VIEW:
152: binList = (LinkedList) viewHistory.get(id);
153: break;
154:
155: case ENTITY:
156: binList = (LinkedList) entityHistory.get(id);
157: break;
158:
159: case SERVICE:
160: binList = (LinkedList) serviceHistory.get(id);
161: break;
162: }
163:
164: if (binList == null) {
165: synchronized (ServerHitBin.class) {
166: switch (type) {
167: case REQUEST:
168: binList = (LinkedList) requestHistory.get(id);
169: break;
170:
171: case EVENT:
172: binList = (LinkedList) eventHistory.get(id);
173: break;
174:
175: case VIEW:
176: binList = (LinkedList) viewHistory.get(id);
177: break;
178:
179: case ENTITY:
180: binList = (LinkedList) entityHistory.get(id);
181: break;
182:
183: case SERVICE:
184: binList = (LinkedList) serviceHistory.get(id);
185: break;
186: }
187: if (binList == null) {
188: binList = new LinkedList();
189: switch (type) {
190: case REQUEST:
191: requestHistory.put(id, binList);
192: break;
193:
194: case EVENT:
195: eventHistory.put(id, binList);
196: break;
197:
198: case VIEW:
199: viewHistory.put(id, binList);
200: break;
201:
202: case ENTITY:
203: entityHistory.put(id, binList);
204: break;
205:
206: case SERVICE:
207: serviceHistory.put(id, binList);
208: break;
209: }
210: }
211: }
212: }
213:
214: if (binList.size() > 0) {
215: bin = (ServerHitBin) binList.getFirst();
216: }
217: if (bin == null) {
218: synchronized (ServerHitBin.class) {
219: if (binList.size() > 0) {
220: bin = (ServerHitBin) binList.getFirst();
221: }
222: if (bin == null) {
223: bin = new ServerHitBin(id, type, true, delegator);
224: binList.addFirst(bin);
225: }
226: }
227: }
228:
229: bin.addHit(startTime, runningTime);
230: if (isOriginal && !"GLOBAL".equals(id)) {
231: bin.saveHit(request, startTime, runningTime, userLogin);
232: }
233:
234: // count since start global and per id hits
235: if (!"GLOBAL".equals(id))
236: countHitSinceStart(id, type, startTime, runningTime,
237: isOriginal, delegator);
238:
239: // also count hits up the hierarchy if the id contains a '.'
240: if (id.indexOf('.') > 0) {
241: countHit(id.substring(0, id.lastIndexOf('.')), type,
242: request, startTime, runningTime, userLogin,
243: delegator, false);
244: }
245:
246: if (isOriginal && !"GLOBAL".equals(id))
247: countHit("GLOBAL", type, request, startTime, runningTime,
248: userLogin, delegator, true);
249: }
250:
251: static void countHitSinceStart(String id, int type, long startTime,
252: long runningTime, boolean isOriginal,
253: GenericDelegator delegator) {
254: if (delegator == null) {
255: throw new IllegalArgumentException(
256: "The delgator passed to countHitSinceStart cannot be null");
257: }
258:
259: ServerHitBin bin = null;
260:
261: // save in global, and try to get bin by id
262: switch (type) {
263: case REQUEST:
264: bin = (ServerHitBin) requestSinceStarted.get(id);
265: break;
266:
267: case EVENT:
268: bin = (ServerHitBin) eventSinceStarted.get(id);
269: break;
270:
271: case VIEW:
272: bin = (ServerHitBin) viewSinceStarted.get(id);
273: break;
274:
275: case ENTITY:
276: bin = (ServerHitBin) entitySinceStarted.get(id);
277: break;
278:
279: case SERVICE:
280: bin = (ServerHitBin) serviceSinceStarted.get(id);
281: break;
282: }
283:
284: if (bin == null) {
285: synchronized (ServerHitBin.class) {
286: switch (type) {
287: case REQUEST:
288: bin = (ServerHitBin) requestSinceStarted.get(id);
289: break;
290:
291: case EVENT:
292: bin = (ServerHitBin) eventSinceStarted.get(id);
293: break;
294:
295: case VIEW:
296: bin = (ServerHitBin) viewSinceStarted.get(id);
297: break;
298:
299: case ENTITY:
300: bin = (ServerHitBin) entitySinceStarted.get(id);
301: break;
302:
303: case SERVICE:
304: bin = (ServerHitBin) serviceSinceStarted.get(id);
305: break;
306: }
307:
308: if (bin == null) {
309: bin = new ServerHitBin(id, type, false, delegator);
310: switch (type) {
311: case REQUEST:
312: requestSinceStarted.put(id, bin);
313: break;
314:
315: case EVENT:
316: eventSinceStarted.put(id, bin);
317: break;
318:
319: case VIEW:
320: viewSinceStarted.put(id, bin);
321: break;
322:
323: case ENTITY:
324: entitySinceStarted.put(id, bin);
325: break;
326:
327: case SERVICE:
328: serviceSinceStarted.put(id, bin);
329: break;
330: }
331: }
332: }
333: }
334:
335: bin.addHit(startTime, runningTime);
336:
337: if (isOriginal)
338: countHitSinceStart("GLOBAL", type, startTime, runningTime,
339: false, delegator);
340: }
341:
342: // these Maps contain Lists of ServerHitBin objects by id, the most recent is first in the list
343: public static Map requestHistory = new HashMap();
344: public static Map eventHistory = new HashMap();
345: public static Map viewHistory = new HashMap();
346: public static Map entityHistory = new HashMap();
347: public static Map serviceHistory = new HashMap();
348:
349: // these Maps contain ServerHitBin objects by id
350: public static Map requestSinceStarted = new HashMap();
351: public static Map eventSinceStarted = new HashMap();
352: public static Map viewSinceStarted = new HashMap();
353: public static Map entitySinceStarted = new HashMap();
354: public static Map serviceSinceStarted = new HashMap();
355:
356: GenericDelegator delegator;
357: String delegatorName;
358: String id;
359: int type;
360: boolean limitLength;
361: long startTime;
362: long endTime;
363: long numberHits;
364: long totalRunningTime;
365: long minTime;
366: long maxTime;
367:
368: public ServerHitBin(String id, int type, boolean limitLength,
369: GenericDelegator delegator) {
370: super ();
371: if (delegator == null) {
372: throw new IllegalArgumentException(
373: "The delgator passed to countHitSinceStart cannot be null");
374: }
375:
376: this .id = id;
377: this .type = type;
378: this .limitLength = limitLength;
379: this .delegator = delegator;
380: this .delegatorName = delegator.getDelegatorName();
381: reset(getEvenStartingTime());
382: }
383:
384: public GenericDelegator getDelegator() {
385: if (this .delegator == null) {
386: this .delegator = GenericDelegator
387: .getGenericDelegator(this .delegatorName);
388: }
389: // if still null, then we have a problem
390: if (this .delegator == null) {
391: throw new IllegalArgumentException(
392: "Could not perform stats operation: could not find delegator with name: "
393: + this .delegatorName);
394: }
395: return this .delegator;
396: }
397:
398: long getEvenStartingTime() {
399: // binLengths should be a divisable evenly into 1 hour
400: long curTime = System.currentTimeMillis();
401: long binLength = getNewBinLength();
402:
403: // find the first previous millis that are even on the hour
404: Calendar cal = Calendar.getInstance();
405:
406: cal.setTime(new Date(curTime));
407: cal.set(Calendar.MINUTE, 0);
408: cal.set(Calendar.SECOND, 0);
409: cal.set(Calendar.MILLISECOND, 0);
410:
411: while (cal.getTime().getTime() < (curTime - binLength)) {
412: cal.add(Calendar.MILLISECOND, (int) binLength);
413: }
414:
415: return cal.getTime().getTime();
416: }
417:
418: static long getNewBinLength() {
419: long binLength = (long) UtilProperties.getPropertyNumber(
420: "serverstats", "stats.bin.length.millis");
421:
422: // if no or 0 binLength specified, set to 30 minutes
423: if (binLength <= 0)
424: binLength = 1800000;
425: // if binLength is more than an hour, set it to one hour
426: if (binLength > 3600000)
427: binLength = 3600000;
428: return binLength;
429: }
430:
431: void reset(long startTime) {
432: this .startTime = startTime;
433: if (limitLength) {
434: long binLength = getNewBinLength();
435:
436: // subtract 1 millisecond to keep bin starting times even
437: this .endTime = startTime + binLength - 1;
438: } else {
439: this .endTime = 0;
440: }
441: this .numberHits = 0;
442: this .totalRunningTime = 0;
443: this .minTime = Long.MAX_VALUE;
444: this .maxTime = 0;
445: }
446:
447: ServerHitBin(ServerHitBin oldBin) {
448: super ();
449:
450: this .id = oldBin.id;
451: this .type = oldBin.type;
452: this .limitLength = oldBin.limitLength;
453: this .delegator = oldBin.delegator;
454: this .delegatorName = oldBin.delegatorName;
455: this .startTime = oldBin.startTime;
456: this .endTime = oldBin.endTime;
457: this .numberHits = oldBin.numberHits;
458: this .totalRunningTime = oldBin.totalRunningTime;
459: this .minTime = oldBin.minTime;
460: this .maxTime = oldBin.maxTime;
461: }
462:
463: public String getId() {
464: return this .id;
465: }
466:
467: public int getType() {
468: return this .type;
469: }
470:
471: public String getTypeString() {
472: return typeNames[this .type];
473: }
474:
475: /** returns the startTime of the bin */
476: public long getStartTime() {
477: return this .startTime;
478: }
479:
480: /** Returns the end time if the length of the bin is limited, otherwise returns the current system time */
481: public long getEndTime() {
482: return limitLength ? this .endTime : System.currentTimeMillis();
483: }
484:
485: /** returns the startTime of the bin */
486: public String getStartTimeString() {
487: // using Timestamp toString because I like the way it formats it
488: return new java.sql.Timestamp(this .getStartTime()).toString();
489: }
490:
491: /** Returns the end time if the length of the bin is limited, otherwise returns the current system time */
492: public String getEndTimeString() {
493: return new java.sql.Timestamp(this .getEndTime()).toString();
494: }
495:
496: /** returns endTime - startTime */
497: public long getBinLength() {
498: return this .getEndTime() - this .getStartTime();
499: }
500:
501: /** returns (endTime - startTime)/60000 */
502: public double getBinLengthMinutes() {
503: return ((double) this .getBinLength()) / 60000.0;
504: }
505:
506: public long getNumberHits() {
507: return this .numberHits;
508: }
509:
510: public long getTotalRunningTime() {
511: return this .totalRunningTime;
512: }
513:
514: public long getMinTime() {
515: return this .minTime;
516: }
517:
518: public double getMinTimeSeconds() {
519: return ((double) this .minTime) / 1000.0;
520: }
521:
522: public long getMaxTime() {
523: return this .maxTime;
524: }
525:
526: public double getMaxTimeSeconds() {
527: return ((double) this .maxTime) / 1000.0;
528: }
529:
530: public double getAvgTime() {
531: return ((double) this .totalRunningTime)
532: / ((double) this .numberHits);
533: }
534:
535: public double getAvgTimeSeconds() {
536: return this .getAvgTime() / 1000.0;
537: }
538:
539: /** return the hits per minute using the entire length of the bin as returned by getBinLengthMinutes() */
540: public double getHitsPerMinute() {
541: return ((double) this .numberHits) / this .getBinLengthMinutes();
542: }
543:
544: synchronized void addHit(long startTime, long runningTime) {
545: advanceBin(startTime + runningTime);
546:
547: this .numberHits++;
548: this .totalRunningTime += runningTime;
549: if (runningTime < this .minTime)
550: this .minTime = runningTime;
551: if (runningTime > this .maxTime)
552: this .maxTime = runningTime;
553: }
554:
555: synchronized void advanceBin(long toTime) {
556: // first check to see if this bin has expired, if so save and recycle it
557: while (limitLength && toTime > this .endTime) {
558: LinkedList binList = null;
559:
560: switch (type) {
561: case REQUEST:
562: binList = (LinkedList) requestHistory.get(id);
563: break;
564:
565: case EVENT:
566: binList = (LinkedList) eventHistory.get(id);
567: break;
568:
569: case VIEW:
570: binList = (LinkedList) viewHistory.get(id);
571: break;
572:
573: case ENTITY:
574: binList = (LinkedList) entityHistory.get(id);
575: break;
576:
577: case SERVICE:
578: binList = (LinkedList) serviceHistory.get(id);
579: break;
580: }
581:
582: // the first in the list will be this object, remove and copy it,
583: // put the copy at the first of the list, then put this object back on
584: binList.removeFirst();
585: if (this .numberHits > 0) {
586: binList.addFirst(new ServerHitBin(this ));
587:
588: // persist each bin when time ends if option turned on
589: if (UtilProperties.propertyValueEqualsIgnoreCase(
590: "serverstats", "stats.persist."
591: + ServerHitBin.typeIds[type] + ".bin",
592: "true")) {
593: GenericValue serverHitBin = delegator.makeValue(
594: "ServerHitBin", null);
595: serverHitBin.set("serverHitBinId", getDelegator()
596: .getNextSeqId("ServerHitBin"));
597: serverHitBin.set("contentId", this .id);
598: serverHitBin.set("hitTypeId",
599: ServerHitBin.typeIds[this .type]);
600: serverHitBin.set("binStartDateTime",
601: new java.sql.Timestamp(this .startTime));
602: serverHitBin.set("binEndDateTime",
603: new java.sql.Timestamp(this .endTime));
604: serverHitBin.set("numberHits", new Long(
605: this .numberHits));
606: serverHitBin.set("totalTimeMillis", new Long(
607: this .totalRunningTime));
608: serverHitBin.set("minTimeMillis", new Long(
609: this .minTime));
610: serverHitBin.set("maxTimeMillis", new Long(
611: this .maxTime));
612: // get localhost ip address and hostname to store
613: try {
614: InetAddress address = InetAddress
615: .getLocalHost();
616:
617: if (address != null) {
618: serverHitBin.set("serverIpAddress", address
619: .getHostAddress());
620: serverHitBin.set("serverHostName", address
621: .getHostName());
622: } else {
623: Debug
624: .logError(
625: "Unable to get localhost internet address, was null",
626: module);
627: }
628: } catch (java.net.UnknownHostException e) {
629: Debug.logError(
630: "Unable to get localhost internet address: "
631: + e.toString(), module);
632: }
633: try {
634: serverHitBin.create();
635: } catch (GenericEntityException e) {
636: Debug.logError(e,
637: "Could not save ServerHitBin:", module);
638: }
639: }
640: }
641: this .reset(this .endTime + 1);
642: binList.addFirst(this );
643: }
644: }
645:
646: void saveHit(HttpServletRequest request, long startTime,
647: long runningTime, GenericValue userLogin) {
648: // persist record of hit in ServerHit entity if option turned on
649: if (UtilProperties.propertyValueEqualsIgnoreCase("serverstats",
650: "stats.persist." + ServerHitBin.typeIds[type] + ".hit",
651: "true")) {
652: // if the hit type is ENTITY and the name contains "ServerHit" don't
653: // persist; avoids the infinite loop and a bunch of annoying data
654: if (this .type == ENTITY && this .id.indexOf("ServerHit") > 0) {
655: return;
656: }
657:
658: // check for type data before running.
659: GenericValue serverHitType = null;
660:
661: try {
662: serverHitType = delegator.findByPrimaryKeyCache(
663: "ServerHitType", UtilMisc.toMap("hitTypeId",
664: ServerHitBin.typeIds[this .type]));
665: } catch (GenericEntityException e) {
666: Debug.logError(e, module);
667: }
668: if (serverHitType == null) {
669: // datamodel data not loaded; not storing hit.
670: Debug.logWarning(
671: "The datamodel data has not been loaded; cannot find hitTypeId '"
672: + ServerHitBin.typeIds[this .type]
673: + " not storing ServerHit.", module);
674: return;
675: }
676:
677: String visitId = VisitHandler.getVisitId(request
678: .getSession());
679:
680: if (visitId == null || visitId.length() == 0) {
681: // no visit info stored, so don't store the ServerHit
682: Debug
683: .logWarning(
684: "Could not find a visitId, so not storing ServerHit. This is probably a configuration error. If you turn of persistance of visits you should also turn off persistence of hits.",
685: module);
686: return;
687: }
688:
689: GenericValue serverHit = delegator.makeValue("ServerHit",
690: null);
691:
692: serverHit.set("visitId", visitId);
693: serverHit.set("hitStartDateTime", new java.sql.Timestamp(
694: startTime));
695: serverHit.set("hitTypeId", ServerHitBin.typeIds[this .type]);
696: if (userLogin != null) {
697: serverHit.set("userLoginId", userLogin
698: .get("userLoginId"));
699: ModelEntity modelUserLogin = userLogin.getModelEntity();
700: if (modelUserLogin.isField("partyId")) {
701: serverHit.set("partyId", userLogin.get("partyId"));
702: }
703: }
704: serverHit.set("contentId", this .id);
705: serverHit.set("runningTimeMillis", new Long(runningTime));
706:
707: String fullRequestUrl = UtilHttp.getFullRequestUrl(request)
708: .toString();
709:
710: serverHit.set("requestUrl",
711: fullRequestUrl.length() > 250 ? fullRequestUrl
712: .substring(0, 250) : fullRequestUrl);
713: String referrerUrl = request.getHeader("Referer") != null ? request
714: .getHeader("Referer")
715: : "";
716:
717: serverHit.set("referrerUrl",
718: referrerUrl.length() > 250 ? referrerUrl.substring(
719: 0, 250) : referrerUrl);
720:
721: // get localhost ip address and hostname to store
722: try {
723: InetAddress address = InetAddress.getLocalHost();
724:
725: if (address != null) {
726: serverHit.set("serverIpAddress", address
727: .getHostAddress());
728: serverHit.set("serverHostName", address
729: .getHostName());
730: } else {
731: Debug
732: .logError(
733: "Unable to get localhost internet address, was null",
734: module);
735: }
736: } catch (java.net.UnknownHostException e) {
737: Debug.logError(
738: "Unable to get localhost internet address: "
739: + e.toString(), module);
740: }
741:
742: try {
743: serverHit.create();
744: } catch (GenericEntityException e) {
745: Debug.logError(e, "Could not save ServerHit:", module);
746: }
747: }
748: }
749: }
|