001: /*
002: * WebSphinx web-crawling toolkit
003: *
004: * Copyright (c) 1998-2002 Carnegie Mellon University. All rights
005: * reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * 1. Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: *
014: * 2. Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * THIS SOFTWARE IS PROVIDED BY CARNEGIE MELLON UNIVERSITY ``AS IS'' AND
020: * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
021: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
022: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY
023: * NOR ITS EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
024: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
025: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
026: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
027: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
028: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
029: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
030: *
031: */
032:
033: package websphinx.workbench;
034:
035: import websphinx.*;
036: import java.awt.*; //#ifdef JDK1.1
037: import java.text.NumberFormat; //#endif JDK1.1
038: import rcm.awt.Constrain;
039: import rcm.util.Mem;
040: import rcm.awt.ClosableFrame;
041: import rcm.awt.BorderPanel;
042:
043: public class Statistics extends Panel implements CrawlListener,
044: Runnable {
045:
046: Crawler crawler;
047: Thread thread;
048: boolean running = false;
049:
050: static final int PAGES_PER_SEC_DECIMAL_PLACES = 1;
051: //#ifdef JDK1.1
052: static final NumberFormat fmtPagesPerSec = NumberFormat
053: .getInstance();
054: static {
055: fmtPagesPerSec
056: .setMinimumFractionDigits(PAGES_PER_SEC_DECIMAL_PLACES);
057: fmtPagesPerSec
058: .setMaximumFractionDigits(PAGES_PER_SEC_DECIMAL_PLACES);
059: }
060: //#endif JDK1.1
061:
062: String runningTime;
063: String activeThreads;
064: String linksTested;
065: String pagesVisited;
066: String pagesPerSec;
067: String pagesLeft;
068: String memoryUsed;
069: String memoryMaxUsed;
070:
071: Button refreshButton;
072:
073: long msecTotal;
074: long timeLastUpdate = -1;
075: long kbMaxUsed;
076:
077: public Statistics() {
078: setLayout(null);
079: add(refreshButton = new Button("Refresh"));
080: update();
081: measureFields(); // to initialize minSize
082: }
083:
084: Image offscreen; // offscreen drawing area
085: Dimension offSize; // size of offscreen buffer
086: Graphics offg; // drawonable associated with offscreen buffer
087: FontMetrics fm; // font metrics for offscreen buffer
088:
089: Dimension minSize = new Dimension();
090:
091: public synchronized void layout() {
092: Dimension d = refreshButton.preferredSize();
093: int x = 0;
094: int y = minSize.height;
095: int w = d.width;
096: int h = d.height;
097: refreshButton.reshape(x, y, w, h);
098: }
099:
100: public Dimension minimumSize() {
101: Dimension d = new Dimension(minSize);
102: d.height += refreshButton.preferredSize().height;
103: return d;
104: }
105:
106: public Dimension preferredSize() {
107: return minimumSize();
108: }
109:
110: public synchronized void update(Graphics g) {
111: // don't clear window with background color first
112: paint(g);
113: }
114:
115: void createOffscreenArea(Dimension d) {
116: offSize = new Dimension(d.width > 0 ? d.width : 1,
117: d.height > 0 ? d.height : 1);
118: offscreen = createImage(offSize.width, offSize.height);
119: offg = offscreen.getGraphics();
120: offg.setFont(getFont());
121: fm = offg.getFontMetrics();
122: }
123:
124: final static int GUTTER = 5;
125:
126: int drawField(Graphics g, int y, String caption, String value) {
127: int cW = fm.stringWidth(caption);
128: int vW = fm.stringWidth(value);
129: minSize.width = Math.max(minSize.width, cW + vW + 10);
130:
131: y += fm.getAscent();
132: g.drawString(caption, 0, y);
133: g.drawString(value, offSize.width - fm.stringWidth(value), y);
134: return fm.getHeight();
135: }
136:
137: void drawFields(Graphics g) {
138: int y = 0;
139: int gutter = GUTTER;
140: //+ Math.max (0, offSize.height - minSize.height) / 8;
141:
142: y += gutter;
143: y += drawField(offg, y, "Running time:", runningTime);
144: y += drawField(offg, y, "Active threads:", activeThreads);
145: y += gutter * 2;
146: y += drawField(offg, y, "Links tested:", linksTested);
147: y += drawField(offg, y, "Links in queue:", pagesLeft);
148: y += gutter * 2;
149: y += drawField(offg, y, "Pages visited:", pagesVisited);
150: y += drawField(offg, y, "Pages/sec:", pagesPerSec);
151: y += gutter * 2;
152: y += drawField(offg, y, "Memory in use:", memoryUsed);
153: y += drawField(offg, y, "Max memory used:", memoryMaxUsed);
154: y += gutter;
155:
156: minSize.height = y;
157: }
158:
159: int measureField(FontMetrics fm, String caption, String value) {
160: int cW = fm.stringWidth(caption);
161: int vW = fm.stringWidth("00000000");
162: minSize.width = Math.max(minSize.width, cW + vW + 10);
163: return fm.getHeight();
164: }
165:
166: void measureFields() {
167: Font font = getFont();
168: if (font == null)
169: font = new Font("Helvetica", Font.PLAIN, 12);
170: FontMetrics fm = Toolkit.getDefaultToolkit().getFontMetrics(
171: font);
172: minSize = new Dimension();
173:
174: int y = 0;
175:
176: y += GUTTER;
177: y += measureField(fm, "Running time:", runningTime);
178: y += measureField(fm, "Active threads:", activeThreads);
179: y += GUTTER * 2;
180: y += measureField(fm, "Links tested:", linksTested);
181: y += measureField(fm, "Links in queue:", pagesLeft);
182: y += GUTTER * 2;
183: y += measureField(fm, "Pages visited:", pagesVisited);
184: y += measureField(fm, "Pages/sec:", pagesPerSec);
185: y += GUTTER * 2;
186: y += measureField(fm, "Memory in use:", memoryUsed);
187: y += measureField(fm, "Max memory used:", memoryMaxUsed);
188: y += GUTTER;
189:
190: minSize.height = y;
191: }
192:
193: Dimension cached;
194:
195: public synchronized void paint(Graphics g) {
196: Dimension d = size();
197: if (cached == null || d.width != cached.width
198: || d.height != cached.height) {
199: g.setColor(getBackground());
200: g.fillRect(0, 0, d.width, d.height);
201: cached = d;
202: }
203:
204: if (offscreen == null)
205: createOffscreenArea(minSize);
206:
207: // erase background
208: offg.setColor(getBackground());
209: offg.fillRect(0, 0, offSize.width, offSize.height);
210:
211: // draw statistics
212: offg.setColor(getForeground());
213: drawFields(offg);
214:
215: // copy to screen
216: g.drawImage(offscreen, 0, 0, null);
217: }
218:
219: public boolean handleEvent(Event event) {
220: if (event.target == refreshButton
221: && event.id == Event.ACTION_EVENT) {
222: Mem.gc();
223: update();
224: } else
225: return super .handleEvent(event);
226: return true;
227: }
228:
229: /**
230: * Reset statistics (primarily the running time, since all other
231: * statistics are computed directly from the crawler's state).
232: * If listening to a crawler, this method is called automatically
233: * when the crawler is cleared.
234: */
235: public synchronized void clear() {
236: msecTotal = 0;
237: timeLastUpdate = -1;
238: update();
239: }
240:
241: /**
242: * Compute the latest statistics. Called automatically by
243: * a background thread when the crawler is running.
244: */
245: public synchronized void update() {
246: long now = System.currentTimeMillis();
247: if (running) {
248: if (timeLastUpdate != -1)
249: msecTotal += (now - timeLastUpdate);
250: timeLastUpdate = now;
251: }
252:
253: int pV, lT, pL, nThreads;
254:
255: if (crawler != null) {
256: lT = crawler.getLinksTested();
257: pV = crawler.getPagesVisited();
258: pL = crawler.getPagesLeft();
259: nThreads = crawler.getActiveThreads();
260: } else {
261: lT = 0;
262: pV = 0;
263: pL = 0;
264: nThreads = 0;
265: }
266:
267: long kbUsed = Mem.used() / 1024;
268: kbMaxUsed = Math.max(kbMaxUsed, kbUsed);
269:
270: double pps = msecTotal > 0 ? (double) pV * 1000 / msecTotal
271: : 0.0;
272:
273: runningTime = formatTime(msecTotal);
274: activeThreads = String.valueOf(nThreads);
275: linksTested = String.valueOf(lT);
276: pagesVisited = String.valueOf(pV);
277: pagesLeft = String.valueOf(pL);
278: pagesPerSec = formatPagesPerSec(pps);
279: memoryUsed = kbUsed + " KB";
280: memoryMaxUsed = kbMaxUsed + " KB";
281:
282: // paint the window NOW
283: Graphics g = getGraphics();
284: if (g != null)
285: update(g);
286: }
287:
288: static String formatTime(long time) {
289: int h, m, s, d;
290: s = (int) (time / 1000);
291: m = s / 60;
292: s %= 60;
293: h = m / 60;
294: m %= 60;
295: d = h / 24;
296: h %= 24;
297: return formatTime(d, h, m, s);
298: }
299:
300: static String formatTime(int d, int h, int m, int s) {
301: return (d > 0 ? d + "d " : "") + (h < 10 ? "0" : "") + h + ":"
302: + (m < 10 ? "0" : "") + m + ":" + (s < 10 ? "0" : "")
303: + s;
304: }
305:
306: static String formatPagesPerSec(double x) {
307: String result;
308:
309: //#ifdef JDK1.1
310: result = fmtPagesPerSec.format(x);
311: //#endif JDK1.1
312:
313: /*#ifdef JDK1.0
314: int places = PAGES_PER_SEC_DECIMAL_PLACES;
315: result = (int)x + ".";
316: if ((int)x == 0 && x < 0)
317: result = "-" + result;
318: x = Math.abs(x);
319: while (places-- > 0) {
320: x = (x - (int)x)*10;
321: result += (int)x;
322: }
323: #endif JDK1.0*/
324:
325: return result;
326: }
327:
328: /**
329: * Start the background thread to update the display. If listening
330: * to a crawler, this method is called automatically when the
331: * crawler starts.
332: */
333: public synchronized void start() {
334: running = true;
335: thread = new Thread(this , "Statistics");
336: thread.setDaemon(true);
337: thread.setPriority(Thread.MIN_PRIORITY);
338: thread.start();
339: }
340:
341: /**
342: * Stop the background thread that updates the display. If listening
343: * to a crawler, this method is called automatically when the
344: * crawler stops.
345: */
346: public synchronized void stop() {
347: running = false;
348: thread = null;
349: timeLastUpdate = -1;
350: }
351:
352: /**
353: * Background thread. Clients shouldn't call this.
354: */
355: public void run() {
356: while (true) {
357: update();
358:
359: if (!running)
360: break;
361:
362: try {
363: Thread.sleep(500);
364: } catch (InterruptedException e) {
365: }
366: }
367: }
368:
369: /**
370: * Notify that the crawler started.
371: */
372: public void started(CrawlEvent event) {
373: crawler = event.getCrawler();
374: start();
375: }
376:
377: /**
378: * Notify that the crawler ran out of links to crawl
379: */
380: public synchronized void stopped(CrawlEvent event) {
381: if (running)
382: stop();
383: }
384:
385: /**
386: * Notify that the crawler's state was cleared.
387: */
388: public void cleared(CrawlEvent event) {
389: clear();
390: }
391:
392: /**
393: * Notify that the crawler timed out.
394: */
395: public void timedOut(CrawlEvent event) {
396: stop();
397: }
398:
399: /**
400: * Notify that the crawler is paused.
401: */
402: public void paused(CrawlEvent event) {
403: stop();
404: }
405:
406: /**
407: * Create a new Frame containing a Statistics panel connected to a crawler.
408: */
409: public static Frame monitor(Crawler crawler) {
410: Frame win = new ClosableFrame("Statistics: "
411: + crawler.getName());
412:
413: Statistics stats = new Statistics();
414: crawler.addCrawlListener(stats);
415:
416: win.add("Center", BorderPanel.wrap(stats, 5, 5, 5, 5));
417: win.pack();
418: win.show();
419:
420: return win;
421: }
422:
423: }
|