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