]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/plugin/SystemInfo.php
Remove CVS backend
[SourceForge/phpwiki.git] / lib / plugin / SystemInfo.php
1 <?php
2
3 /**
4  * Copyright (C) 1999, 2000, 2001, 2002 $ThePhpWikiProgrammingTeam
5  * Copyright 2008-2009 Marc-Etienne Vargenau, Alcatel-Lucent
6  *
7  * This file is part of PhpWiki.
8  *
9  * PhpWiki is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * PhpWiki is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License along
20  * with PhpWiki; if not, write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22  */
23
24 /**
25  * Usage: <<SystemInfo all >>
26  *        or <<SystemInfo pagestats cachestats discspace hitstats >>
27  *        or <<SystemInfo version >>
28  *        or <<SystemInfo current_theme >>
29  *        or <<SystemInfo PHPWIKI_DIR >>
30  *
31  * Provide access to phpwiki's lower level system information.
32  *
33  *   version, pagestats, SERVER_NAME, database, discspace,
34  *   cachestats, userstats, linkstats, accessstats, hitstats,
35  *   revisionstats, interwikilinks, imageextensions, wikiwordregexp,
36  *   availableplugins, downloadurl  or any other predefined CONSTANT
37  *
38  * In spirit to http://www.ecyrd.com/JSPWiki/SystemInfo.jsp
39  *
40  * Done: Some calculations are heavy (~5-8 secs), so we should cache
41  *       the result. In the page or with WikiPluginCached?
42  */
43
44 require_once 'lib/WikiPluginCached.php';
45
46 class WikiPlugin_SystemInfo
47     extends WikiPluginCached
48 {
49     public $_dbi;
50
51     function getPluginType()
52     {
53         return PLUGIN_CACHED_HTML;
54     }
55
56     function getDescription()
57     {
58         return _("Provide access to PhpWiki's lower level system information.");
59     }
60
61     /* From lib/WikiPlugin.php:
62      * If the plugin can deduce a modification time, or equivalent
63      * sort of tag for it's content, then the plugin should
64      * call $request->appendValidators() with appropriate arguments,
65      * and should override this method to return true.
66      */
67     function managesValidators()
68     {
69         return true;
70     }
71
72     function getExpire($dbi, $argarray, $request)
73     {
74         return '+1800'; // 30 minutes
75     }
76
77     /**
78      * @param WikiDB $dbi
79      * @param array $argarray
80      * @param WikiRequest $request
81      * @param string $basepage
82      * @return mixed
83      */
84     protected function getHtml($dbi, $argarray, $request, $basepage)
85     {
86         $loader = new WikiPluginLoader();
87         return $loader->expandPI('<<SystemInfo '
88             . WikiPluginCached::glueArgs($argarray) // all
89             . ' ?>', $request, $this, $basepage);
90     }
91
92     protected function getImage($dbi, $argarray, $request)
93     {
94         trigger_error('pure virtual', E_USER_ERROR);
95     }
96
97     protected function getMap($dbi, $argarray, $request)
98     {
99         trigger_error('pure virtual', E_USER_ERROR);
100     }
101
102     function getDefaultArguments()
103     {
104         return array( // 'separator' => ' ', // on multiple args
105         );
106     }
107
108     function database()
109     {
110         $s = "DATABASE_TYPE: " . DATABASE_TYPE . ", ";
111         switch (DATABASE_TYPE) {
112             case 'SQL': // pear
113             case 'ADODB':
114             case 'PDO':
115                 $dsn = DATABASE_DSN;
116                 $s .= "DATABASE BACKEND:" . " ";
117                 $s .= (DATABASE_TYPE == 'SQL') ? 'PearDB' : 'ADODB';
118                 if (preg_match('/^(\w+):/', $dsn, $m)) {
119                     $backend = $m[1];
120                     $s .= " $backend";
121                 }
122                 $s .= ", DATABASE_PREFIX: \"" . DATABASE_PREFIX . "\", ";
123                 break;
124             case 'dba':
125                 $s .= "DATABASE_DBA_HANDLER: " . DATABASE_DBA_HANDLER . ", ";
126                 $s .= "DATABASE_DIRECTORY: \"" . DATABASE_DIRECTORY . "\", ";
127                 break;
128             case 'flatfile':
129                 $s .= "DATABASE_DIRECTORY: " . DATABASE_DIRECTORY . ", ";
130                 break;
131         }
132         // hack: suppress error when using sql, so no timeout
133         @$s .= "DATABASE_TIMEOUT: " . DATABASE_TIMEOUT;
134         return $s;
135     }
136
137     function cachestats()
138     {
139         if (!defined('USECACHE') or !USECACHE)
140             return _("no cache used");
141         $dbi =& $this->_dbi;
142         $cache = $dbi->_cache;
143         $s = _("cached pagedata:") . " " . count($cache->_pagedata_cache);
144         $s .= ", " . _("cached versiondata:");
145         $s .= " " . count($cache->_versiondata_cache);
146         //$s .= ", glv size: " . count($cache->_glv_cache);
147         //$s .= ", cache hits: ?";
148         //$s .= ", cache misses: ?";
149         return $s;
150     }
151
152     function ExpireParams()
153     {
154         global $ExpireParams;
155         $s = sprintf(_("Keep up to %d major edits, but keep them no longer than %d days."),
156             $ExpireParams['major']['keep'],
157             $ExpireParams['major']['max_age']);
158         $s .= sprintf(_(" Keep up to %d minor edits, but keep them no longer than %d days."),
159             $ExpireParams['minor']['keep'],
160             $ExpireParams['minor']['max_age']);
161         $s .= sprintf(_(" Keep the latest contributions of the last %d authors up to %d days."),
162             $ExpireParams['author']['keep'], $ExpireParams['author']['max_age']);
163         $s .= sprintf(_(" Additionally, try to keep the latest contributions of all authors in the last %d days (even if there are more than %d of them,) but in no case keep more than %d unique author revisions."),
164             $ExpireParams['author']['min_age'],
165             $ExpireParams['author']['keep'],
166             $ExpireParams['author']['max_keep']);
167         return $s;
168     }
169
170     function pagestats()
171     {
172         global $request;
173         $dbi = $request->getDbh();
174         $s = sprintf(_("%d pages"), $dbi->numPages(true));
175         $s .= ", " . sprintf(_("%d not-empty pages"), $dbi->numPages(false));
176         // more bla....
177         // $s  .= ", " . sprintf(_("earliest page from %s"), $earliestdate);
178         // $s  .= ", " . sprintf(_("latest page from %s"), $latestdate);
179         // $s  .= ", " . sprintf(_("latest pagerevision from %s"), $latestrevdate);
180         return $s;
181     }
182
183     //What kind of link statistics?
184     //  total links in, total links out, mean links per page, ...
185     //  Any useful numbers similar to a VisualWiki interestmap?
186     function linkstats()
187     {
188         $s = _("not yet");
189         return $s;
190     }
191
192     // number of homepages: easy
193     // number of anonymous users?
194     //   calc this from accesslog info?
195     // number of anonymous edits?
196     //   easy. related to the view/edit rate in accessstats.
197     function userstats()
198     {
199         $dbi =& $this->_dbi;
200         $h = 0;
201         $page_iter = $dbi->getAllPages(true);
202         while ($page = $page_iter->next()) {
203             if ($page->isUserPage(true)) // check if the admin is there. if not add him to the authusers.
204                 $h++;
205         }
206         $s = sprintf(_("%d homepages"), $h);
207         // $s  .= ", " . sprintf(_("%d anonymous users"), $au); // ??
208         // $s  .= ", " . sprintf(_("%d anonymous edits"), $ae); // see recentchanges
209         // $s  .= ", " . sprintf(_("%d authenticated users"), $auth); // users with password set
210         // $s  .= ", " . sprintf(_("%d externally authenticated users"), $extauth); // query AuthDB?
211         return $s;
212     }
213
214     //only from logging info possible. = hitstats per time.
215     // total hits per day/month/year
216     // view/edit rate
217     // TODO: see WhoIsOnline hit stats, and sql accesslogs
218     function accessstats()
219     {
220         $s = _("not yet");
221         return $s;
222     }
223
224     // numeric array
225     private function get_stats($hits, $treshold = 10.0)
226     {
227         sort($hits);
228         reset($hits);
229         $n = count($hits);
230         $max = 0;
231         $min = 9999999999999;
232         $sum = 0;
233         foreach ($hits as $h) {
234             $sum += $h;
235             $max = max($h, $max);
236             $min = min($h, $min);
237         }
238         $mean = $n ? $sum / $n : 0;
239         $median = $hits[ (int)($n / 2) ];
240         $mintreshold = $max * $treshold / 100.0; // lower than 10% of the hits
241         reset($hits);
242         $nmin = $hits[0] < $mintreshold ? 1 : 0;
243         while (next($hits) < $mintreshold)
244             $nmin++;
245         $maxtreshold = $max - $mintreshold; // more than 90% of the hits
246         end($hits);
247         $nmax = 1;
248         while (prev($hits) > $maxtreshold)
249             $nmax++;
250         return array('n' => $n,
251             'sum' => $sum,
252             'min' => $min,
253             'max' => $max,
254             'mean' => $mean,
255             'median' => $median,
256             'stddev' => stddev($hits, $sum),
257             'treshold' => $treshold,
258             'nmin' => $nmin,
259             'mintreshold' => $mintreshold,
260             'nmax' => $nmax,
261             'maxtreshold' => $maxtreshold);
262     }
263
264     // only absolute numbers, not for any time interval. see accessstats
265     //  some useful number derived from the curve of the hit stats.
266     //  total, max, mean, median, stddev;
267     //  %d pages less than 3 hits (<10%)    <10% percent of the leastpopular
268     //  %d pages more than 100 hits (>90%)  >90% percent of the mostpopular
269     function hitstats()
270     {
271         $dbi =& $this->_dbi;
272         $hits = array();
273         $page_iter = $dbi->getAllPages(true);
274         while ($page = $page_iter->next()) {
275             if (($current = $page->getCurrentRevision())
276                 && (!$current->hasDefaultContents())
277             ) {
278                 $hits[] = $page->get('hits');
279             }
280         }
281         $treshold = 10.0;
282         $stats = $this->get_stats($hits, $treshold);
283
284         $s = sprintf(_("total hits: %d"), $stats['sum']);
285         $s .= ", " . sprintf(_("max: %d"), $stats['max']);
286         $s .= ", " . sprintf(_("mean: %2.3f"), $stats['mean']);
287         $s .= ", " . sprintf(_("median: %d"), $stats['median']);
288         $s .= ", " . sprintf(_("stddev: %2.3f"), $stats['stddev']);
289         $s .= "; " . sprintf(_("%d pages with less than %d hits (<%d%%)."),
290             $stats['nmin'], $stats['mintreshold'], $treshold);
291         $s .= " " . sprintf(_("%d page(s) with more than %d hits (>%d%%)."),
292             $stats['nmax'], $stats['maxtreshold'], 100 - $treshold);
293         return $s;
294     }
295
296     /* not yet ready
297      */
298     function revisionstats()
299     {
300         global $LANG;
301
302         include_once 'lib/WikiPluginCached.php';
303         $cache = WikiPluginCached::newCache();
304         $id = $cache->generateId('SystemInfo::revisionstats_' . $LANG);
305         $cachedir = 'plugincache';
306         $content = $cache->get($id, $cachedir);
307
308         if (!empty($content))
309             return $content;
310
311         $dbi =& $this->_dbi;
312         $stats = array();
313         $page_iter = $dbi->getAllPages(true);
314         $stats['empty'] = $stats['latest']['major'] = $stats['latest']['minor'] = 0;
315         while ($page = $page_iter->next()) {
316             if (!$page->exists()) {
317                 $stats['empty']++;
318                 continue;
319             }
320             $current = $page->getCurrentRevision();
321             // is the latest revision a major or minor one?
322             //   latest revision: numpages 200 (100%) / major (60%) / minor (40%)
323             if ($current->get('is_minor_edit'))
324                 $stats['latest']['major']++;
325             else
326                 $stats['latest']['minor']++;
327             /*
328                         // FIXME: This needs much too long to be acceptable.
329                         // overall:
330                         //   number of revisions: all (100%) / major (60%) / minor (40%)
331                         // revs per page:
332                         //   per page: mean 20 / major (60%) / minor (40%)
333                         $rev_iter = $page->getAllRevisions();
334                         while ($rev = $rev_iter->next()) {
335                             if ($rev->get('is_minor_edit'))
336                                 $stats['page']['major']++;
337                             else
338                                 $stats['page']['minor']++;
339                         }
340                         $rev_iter->free();
341                         $stats['page']['all'] = $stats['page']['major'] + $stats['page']['minor'];
342                         $stats['perpage'][]       = $stats['page']['all'];
343                         $stats['perpage_major'][] = $stats['page']['major'];
344                         $stats['sum']['all'] += $stats['page']['all'];
345                         $stats['sum']['major'] += $stats['page']['major'];
346                         $stats['sum']['minor'] += $stats['page']['minor'];
347                         $stats['page'] = array();
348             */
349         }
350         $page_iter->free();
351         $stats['numpages'] = $stats['latest']['major'] + $stats['latest']['minor'];
352         $stats['latest']['major_perc'] = $stats['latest']['major'] * 100.0 / $stats['numpages'];
353         $stats['latest']['minor_perc'] = $stats['latest']['minor'] * 100.0 / $stats['numpages'];
354         $empty = sprintf("empty pages: %d (%02.1f%%) / %d (100%%)\n",
355             $stats['empty'], $stats['empty'] * 100.0 / $stats['numpages'],
356             $stats['numpages']);
357         $latest = sprintf("latest revision: major %d (%02.1f%%) / minor %d (%02.1f%%) / all %d (100%%)\n",
358             $stats['latest']['major'], $stats['latest']['major_perc'],
359             $stats['latest']['minor'], $stats['latest']['minor_perc'], $stats['numpages']);
360         /*
361                 $stats['sum']['major_perc'] = $stats['sum']['major'] * 100.0 / $stats['sum']['all'];
362                 $stats['sum']['minor_perc'] = $stats['sum']['minor'] * 100.0 / $stats['sum']['all'];
363                 $sum = sprintf("number of revisions: major %d (%02.1f%%) / minor %d (%02.1f%%) / all %d (100%%)\n",
364                                $stats['sum']['major'], $stats['sum']['major_perc'],
365                                $stats['sum']['minor'], $stats['sum']['minor_perc'], $stats['sum']['all']);
366
367                 $stats['perpage']       = $this->get_stats($stats['perpage']);
368                 $stats['perpage_major'] = $this->get_stats($stats['perpage_major']);
369                 $stats['perpage']['major_perc'] = $stats['perpage_major']['sum'] * 100.0 / $stats['perpage']['sum'];
370                 $stats['perpage']['minor_perc'] = 100 - $stats['perpage']['major_perc'];
371                 $stats['perpage_minor']['sum']  = $stats['perpage']['sum'] - $stats['perpage_major']['sum'];
372                 $stats['perpage_minor']['mean'] = $stats['perpage_minor']['sum'] / ($stats['perpage']['n'] - $stats['perpage_major']['n']);
373                 $perpage = sprintf("revisions per page: all %d, mean %02.1f / major %d (%02.1f%%) / minor %d (%02.1f%%)\n",
374                                    $stats['perpage']['sum'], $stats['perpage']['mean'],
375                                    $stats['perpage_major']['mean'], $stats['perpage']['major_perc'],
376                                    $stats['perpage_minor']['mean'], $stats['perpage']['minor_perc']);
377                 $perpage .= sprintf("  %d page(s) with less than %d revisions (<%d%%)\n",
378                                     $stats['perpage']['nmin'], $stats['perpage']['maintreshold'], $treshold);
379                 $perpage .= sprintf("  %d page(s) with more than %d revisions (>%d%%)\n",
380                                     $stats['perpage']['nmax'], $stats['perpage']['maxtreshold'], 100 - $treshold);
381                 $content = $empty . $latest . $sum . $perpage;
382         */
383         $content = $empty . $latest;
384
385         // regenerate cache every 30 minutes
386         $cache->save($id, $content, '+1800', $cachedir);
387         return $content;
388     }
389
390     // Size of databases/files are possible plus the known size of the app.
391     // Cache this costly operation.
392     // Even if the whole plugin call is stored internally, we cache this
393     // separately with a separate key.
394     function discspace()
395     {
396         global $DBParams;
397
398         include_once 'lib/WikiPluginCached.php';
399         $cache = WikiPluginCached::newCache();
400         $id = $cache->generateId('SystemInfo::discspace');
401         $cachedir = 'plugincache';
402         $content = $cache->get($id, $cachedir);
403
404         if (empty($content)) {
405             $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : '.';
406             //TODO: windows only (no cygwin)
407             $appsize = `du -s $dir | cut -f1`;
408
409             if (in_array($DBParams['dbtype'], array('SQL', 'ADODB'))) {
410                 //TODO: where is the data is actually stored? see phpMyAdmin
411                 $pagesize = 0;
412             } elseif ($DBParams['dbtype'] == 'dba') {
413                 $pagesize = 0;
414                 $dbdir = $DBParams['directory'];
415                 if ($DBParams['dba_handler'] == 'db3')
416                     $pagesize = filesize($DBParams['directory']
417                         . "/wiki_pagedb.db3") / 1024;
418                 // if issubdirof($dbdir, $dir) $appsize -= $pagesize;
419             } else { // flatfile
420                 $dbdir = $DBParams['directory'];
421                 $pagesize = `du -s $dbdir`;
422                 // if issubdirof($dbdir, $dir) $appsize -= $pagesize;
423             }
424             $content = array('appsize' => $appsize,
425                 'pagesize' => $pagesize);
426             // regenerate cache every 30 minutes
427             $cache->save($id, $content, '+1800', $cachedir);
428         } else {
429             $appsize = $content['appsize'];
430             $pagesize = $content['pagesize'];
431         }
432
433         $s = sprintf(_("Application size: %d KiB"), $appsize);
434         if ($pagesize)
435             $s .= ", " . sprintf(_("Pagedata size: %d KiB", $pagesize));
436         return $s;
437     }
438
439     function inlineimages()
440     {
441         return implode(' ', explode('|', INLINE_IMAGES));
442     }
443
444     function wikinameregexp()
445     {
446         return $GLOBALS['WikiNameRegexp'];
447     }
448
449     function allowedprotocols()
450     {
451         return implode(' ', explode('|', ALLOWED_PROTOCOLS));
452     }
453
454     function available_plugins()
455     {
456         $fileset = new FileSet(FindFile('lib/plugin'), '*.php');
457         $list = $fileset->getFiles();
458         natcasesort($list);
459         reset($list);
460         return sprintf(_("Total %d plugins: "), count($list))
461             . implode(', ', array_map(create_function('$f',
462                     'return substr($f,0,-4);'),
463                 $list));
464     }
465
466     function supported_languages()
467     {
468         $available_languages = listAvailableLanguages();
469         natcasesort($available_languages);
470
471         return sprintf(_("Total of %d languages: "),
472             count($available_languages))
473             . implode(', ', $available_languages) . ". "
474             . _("Current language") . _(": ") . $GLOBALS['LANG']
475             . ((DEFAULT_LANGUAGE != $GLOBALS['LANG'])
476                 ? ". " . _("Default language") . _(": ") . DEFAULT_LANGUAGE
477                 : '');
478     }
479
480     function supported_themes()
481     {
482         global $WikiTheme;
483         $available_themes = listAvailableThemes();
484         natcasesort($available_themes);
485         return sprintf(_("Total of %d themes: "), count($available_themes))
486             . implode(', ', $available_themes) . ". "
487             . _("Current theme") . _(": ") . $WikiTheme->_name
488             . ((THEME != $WikiTheme->_name)
489                 ? ". " . _("Default theme") . _(": ") . THEME
490                 : '');
491     }
492
493     function call($arg, &$availableargs)
494     {
495         if (!empty($availableargs[$arg]))
496             return $availableargs[$arg]();
497         elseif (method_exists($this, $arg)) // any defined SystemInfo->method()
498             return call_user_func_array(array(&$this, $arg), array()); elseif (defined($arg) && // any defined constant
499             !in_array($arg, array('ADMIN_PASSWD', 'DATABASE_DSN', 'DBAUTH_AUTH_DSN'))
500         )
501             return constant($arg); else
502             return $this->error(sprintf(_("unknown argument ā€œ%sā€ to SystemInfo"), $arg));
503     }
504
505     /**
506      * @param WikiDB $dbi
507      * @param string $argstr
508      * @param WikiRequest $request
509      * @param string $basepage
510      * @return mixed
511      */
512     function run($dbi, $argstr, &$request, $basepage)
513     {
514         // don't parse argstr for name=value pairs. instead we use just 'name'
515         //$args = $this->getArgs($argstr, $request);
516         $this->_dbi =& $dbi;
517         $args['separator'] = ' ';
518         $availableargs = // name => callback + 0 args
519             array('appname' => create_function('', "return 'PhpWiki';"),
520                 'version' => create_function('', "return sprintf('%s', PHPWIKI_VERSION);"),
521                 'LANG' => create_function('', 'return $GLOBALS["LANG"];'),
522                 'LC_ALL' => create_function('', 'return setlocale(LC_ALL, 0);'),
523                 'current_language' => create_function('', 'return $GLOBALS["LANG"];'),
524                 'system_language' => create_function('', 'return DEFAULT_LANGUAGE;'),
525                 'current_theme' => create_function('', 'return $GLOBALS["WikiTheme"]->_name;'),
526                 'system_theme' => create_function('', 'return THEME;'),
527                 // more here or as method.
528                 '' => create_function('', "return 'dummy';")
529             );
530         // split the argument string by any number of commas or space
531         // characters, which include " ", \r, \t, \n and \f
532         $allargs = preg_split("/[\s,]+/", $argstr, -1, PREG_SPLIT_NO_EMPTY);
533         if (in_array('all', $allargs) || in_array('table', $allargs)) {
534             $allargs = array('appname' => _("Application name"),
535                 'version' => _("PhpWiki engine version"),
536                 'database' => _("Database"),
537                 'cachestats' => _("Cache statistics"),
538                 'pagestats' => _("Page statistics"),
539                 //'revisionstats'    => _("Page revision statistics"),
540                 //'linkstats'        => _("Link statistics"),
541                 'userstats' => _("User statistics"),
542                 //'accessstats'      => _("Access statistics"),
543                 'hitstats' => _("Hit statistics"),
544                 'discspace' => _("Harddisc usage"),
545                 'expireparams' => _("Expiry parameters"),
546                 'wikinameregexp' => _("Wikiname regexp"),
547                 'allowedprotocols' => _("Allowed protocols"),
548                 'inlineimages' => _("Inline images"),
549                 'available_plugins' => _("Available plugins"),
550                 'supported_languages' => _("Supported languages"),
551                 'supported_themes' => _("Supported themes"),
552 //                           '' => _(""),
553                 '' => ""
554             );
555             $table = HTML::table(array('class' => 'bordered'));
556             foreach ($allargs as $arg => $desc) {
557                 if (!$arg)
558                     continue;
559                 if (!$desc)
560                     $desc = _($arg);
561                 $table->pushContent(HTML::tr(HTML::th(array('style' => "white-space:nowrap"), $desc),
562                     HTML::td(HTML($this->call($arg, $availableargs)))));
563             }
564             return $table;
565         } else {
566             $output = '';
567             foreach ($allargs as $arg) {
568                 $o = $this->call($arg, $availableargs);
569                 if (is_object($o))
570                     return $o;
571                 else
572                     $output .= ($o . $args['separator']);
573             }
574             // if more than one arg, remove the trailing separator
575             if ($output) $output = substr($output, 0,
576                 -strlen($args['separator']));
577             return HTML($output);
578         }
579     }
580 }
581
582 function median($hits)
583 {
584     sort($hits);
585     reset($hits);
586     $n = count($hits);
587     $median = (int)($n / 2);
588     if (!($n % 2)) // proper rounding on even length
589         return ($hits[$median] + $hits[$median - 1]) * 0.5;
590     else
591         return $hits[$median];
592 }
593
594 function rsum($a, $b)
595 {
596     $a += $b;
597     return $a;
598 }
599
600 function mean(&$hits, $total = false)
601 {
602     $n = count($hits);
603     if (!$total)
604         $total = array_reduce($hits, 'rsum');
605     return (float)$total / ($n * 1.0);
606 }
607
608 function stddev(&$hits, $total = false)
609 {
610     $n = count($hits);
611     if (!$total) $total = array_reduce($hits, 'rsum');
612     $GLOBALS['mean'] = $total / $n;
613     $r = array_map(create_function('$i', 'global $mean; return ($i-$mean)*($i-$mean);'),
614         $hits);
615     unset($GLOBALS['mean']);
616     return (float)sqrt(mean($r, $total) * ($n / (float)($n - 1)));
617 }
618
619 // Local Variables:
620 // mode: php
621 // tab-width: 8
622 // c-basic-offset: 4
623 // c-hanging-comment-ender-p: nil
624 // indent-tabs-mode: nil
625 // End: