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