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