4 * Copyright (C) 1999, 2000, 2001, 2002 $ThePhpWikiProgrammingTeam
5 * Copyright 2008-2009 Marc-Etienne Vargenau, Alcatel-Lucent
7 * This file is part of PhpWiki.
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.
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.
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.
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 >>
31 * Provide access to phpwiki's lower level system information.
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
38 * In spirit to http://www.ecyrd.com/JSPWiki/SystemInfo.jsp
40 * Done: Some calculations are heavy (~5-8 secs), so we should cache
41 * the result. In the page or with WikiPluginCached?
44 require_once 'lib/WikiPluginCached.php';
46 class WikiPlugin_SystemInfo
47 extends WikiPluginCached
51 function getPluginType()
53 return PLUGIN_CACHED_HTML;
56 function getDescription()
58 return _("Provide access to PhpWiki's lower level system information.");
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.
67 function managesValidators()
72 function getExpire($dbi, $argarray, $request)
74 return '+1800'; // 30 minutes
79 * @param array $argarray
80 * @param WikiRequest $request
81 * @param string $basepage
84 protected function getHtml($dbi, $argarray, $request, $basepage)
86 $loader = new WikiPluginLoader();
87 return $loader->expandPI('<<SystemInfo '
88 . WikiPluginCached::glueArgs($argarray) // all
89 . ' ?>', $request, $this, $basepage);
92 protected function getImage($dbi, $argarray, $request)
94 trigger_error('pure virtual', E_USER_ERROR);
97 protected function getMap($dbi, $argarray, $request)
99 trigger_error('pure virtual', E_USER_ERROR);
102 function getDefaultArguments()
104 return array( // 'separator' => ' ', // on multiple args
110 $s = "DATABASE_TYPE: " . DATABASE_TYPE . ", ";
111 switch (DATABASE_TYPE) {
116 $s .= "DATABASE BACKEND:" . " ";
117 $s .= (DATABASE_TYPE == 'SQL') ? 'PearDB' : 'ADODB';
118 if (preg_match('/^(\w+):/', $dsn, $m)) {
122 $s .= ", DATABASE_PREFIX: \"" . DATABASE_PREFIX . "\", ";
125 $s .= "DATABASE_DBA_HANDLER: " . DATABASE_DBA_HANDLER . ", ";
126 $s .= "DATABASE_DIRECTORY: \"" . DATABASE_DIRECTORY . "\", ";
129 $s .= "DATABASE_DIRECTORY: " . DATABASE_DIRECTORY . ", ";
132 // hack: suppress error when using sql, so no timeout
133 @$s .= "DATABASE_TIMEOUT: " . DATABASE_TIMEOUT;
137 function cachestats()
139 if (!defined('USECACHE') or !USECACHE)
140 return _("no cache used");
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: ?";
152 function ExpireParams()
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']);
173 $dbi = $request->getDbh();
174 $s = sprintf(_("%d pages"), $dbi->numPages(true));
175 $s .= ", " . sprintf(_("%d not-empty pages"), $dbi->numPages(false));
177 // $s .= ", " . sprintf(_("earliest page from %s"), $earliestdate);
178 // $s .= ", " . sprintf(_("latest page from %s"), $latestdate);
179 // $s .= ", " . sprintf(_("latest pagerevision from %s"), $latestrevdate);
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?
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.
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.
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?
214 //only from logging info possible. = hitstats per time.
215 // total hits per day/month/year
217 // TODO: see WhoIsOnline hit stats, and sql accesslogs
218 function accessstats()
225 private function get_stats($hits, $treshold = 10.0)
231 $min = 9999999999999;
233 foreach ($hits as $h) {
235 $max = max($h, $max);
236 $min = min($h, $min);
238 $mean = $n ? $sum / $n : 0;
239 $median = $hits[ (int)($n / 2) ];
240 $mintreshold = $max * $treshold / 100.0; // lower than 10% of the hits
242 $nmin = $hits[0] < $mintreshold ? 1 : 0;
243 while (next($hits) < $mintreshold)
245 $maxtreshold = $max - $mintreshold; // more than 90% of the hits
248 while (prev($hits) > $maxtreshold)
250 return array('n' => $n,
256 'stddev' => stddev($hits, $sum),
257 'treshold' => $treshold,
259 'mintreshold' => $mintreshold,
261 'maxtreshold' => $maxtreshold);
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
273 $page_iter = $dbi->getAllPages(true);
274 while ($page = $page_iter->next()) {
275 if (($current = $page->getCurrentRevision())
276 && (!$current->hasDefaultContents())
278 $hits[] = $page->get('hits');
282 $stats = $this->get_stats($hits, $treshold);
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);
298 function revisionstats()
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);
308 if (!empty($content))
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()) {
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']++;
326 $stats['latest']['minor']++;
328 // FIXME: This needs much too long to be acceptable.
330 // number of revisions: all (100%) / major (60%) / minor (40%)
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']++;
338 $stats['page']['minor']++;
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();
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'],
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']);
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']);
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;
383 $content = $empty . $latest;
385 // regenerate cache every 30 minutes
386 $cache->save($id, $content, '+1800', $cachedir);
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.
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);
404 if (empty($content)) {
405 $dir = defined('PHPWIKI_DIR') ? PHPWIKI_DIR : '.';
406 //TODO: windows only (no cygwin)
407 $appsize = `du -s $dir | cut -f1`;
409 if (in_array($DBParams['dbtype'], array('SQL', 'ADODB'))) {
410 //TODO: where is the data is actually stored? see phpMyAdmin
412 } elseif ($DBParams['dbtype'] == 'dba') {
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;
420 $dbdir = $DBParams['directory'];
421 $pagesize = `du -s $dbdir`;
422 // if issubdirof($dbdir, $dir) $appsize -= $pagesize;
424 $content = array('appsize' => $appsize,
425 'pagesize' => $pagesize);
426 // regenerate cache every 30 minutes
427 $cache->save($id, $content, '+1800', $cachedir);
429 $appsize = $content['appsize'];
430 $pagesize = $content['pagesize'];
433 $s = sprintf(_("Application size: %d KiB"), $appsize);
435 $s .= ", " . sprintf(_("Pagedata size: %d KiB", $pagesize));
439 function inlineimages()
441 return implode(' ', explode('|', INLINE_IMAGES));
444 function wikinameregexp()
446 return $GLOBALS['WikiNameRegexp'];
449 function allowedprotocols()
451 return implode(' ', explode('|', ALLOWED_PROTOCOLS));
454 function available_plugins()
456 $fileset = new FileSet(FindFile('lib/plugin'), '*.php');
457 $list = $fileset->getFiles();
460 return sprintf(_("Total %d plugins: "), count($list))
461 . implode(', ', array_map(create_function('$f',
462 'return substr($f,0,-4);'),
466 function supported_languages()
468 $available_languages = listAvailableLanguages();
469 natcasesort($available_languages);
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
480 function supported_themes()
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
493 function call($arg, &$availableargs)
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'))
501 return constant($arg); else
502 return $this->error(sprintf(_("unknown argument ā%sā to SystemInfo"), $arg));
507 * @param string $argstr
508 * @param WikiRequest $request
509 * @param string $basepage
512 function run($dbi, $argstr, &$request, $basepage)
514 // don't parse argstr for name=value pairs. instead we use just 'name'
515 //$args = $this->getArgs($argstr, $request);
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';")
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"),
555 $table = HTML::table(array('class' => 'bordered'));
556 foreach ($allargs as $arg => $desc) {
561 $table->pushContent(HTML::tr(HTML::th(array('style' => "white-space:nowrap"), $desc),
562 HTML::td(HTML($this->call($arg, $availableargs)))));
567 foreach ($allargs as $arg) {
568 $o = $this->call($arg, $availableargs);
572 $output .= ($o . $args['separator']);
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);
582 function median($hits)
587 $median = (int)($n / 2);
588 if (!($n % 2)) // proper rounding on even length
589 return ($hits[$median] + $hits[$median - 1]) * 0.5;
591 return $hits[$median];
594 function rsum($a, $b)
600 function mean(&$hits, $total = false)
604 $total = array_reduce($hits, 'rsum');
605 return (float)$total / ($n * 1.0);
608 function stddev(&$hits, $total = false)
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);'),
615 unset($GLOBALS['mean']);
616 return (float)sqrt(mean($r, $total) * ($n / (float)($n - 1)));
623 // c-hanging-comment-ender-p: nil
624 // indent-tabs-mode: nil