在一個 Oracle 數據庫中,DBA 經常要尋找快要用完的表空間,然后要么增加一個新的數據文件,要么刪除舊的數據,從而保證應用程序不至于出于因空間錯誤而失敗。我想將treemaps應用到這個問題上可能非常有趣。 我發現到從一個數據庫查詢中產生一個圖形圖像在各種環境下有區別,因此我決定將這分兩部分寫這篇文章。 第一部分討論使用 php 文件產生 PNG 圖像;第二部分則分析使用modplsql從 PL/SQL 產生一個 SVG 格式的圖像。大多數瀏覽器都支持 PNG 格式的圖像文件,但是并不是每個人都安裝了 PHP 服務器。然而將相同的代碼應用到其它服務器上相當簡單,比如說應用到 java servlet上,Java servlet具有動態產生圖像文件的能力。 有幾種不同的算法可以用來產生treemap.我選擇 pivot 算法,該算法能夠避免一個常見的問題,即非常小的數據會擠到另外一個小區域內。 在這個例子中,我想要產生一個treemap,使用該treemap來包含一個矩形表示每一個表空間。每個矩形都用綠色、黃色和紅色三種顏色不同的深淺度標出,并用此法顯示在每個表空間中還有多少自由空間。綠色表空間表示有很多自由空間,黃色表空間表示自由空間還有一半,紅色表空間表示已經幾乎沒有自由空間。在每個基本顏色之間我們使用不同的深淺度。(相比查看一大堆表空間大小和百分比來說這樣可讀性更好。) 數據和表現之間可能的結合有無數種。Treemaps非常擅長顯示有很多條目的數據。對這個例子進行擴展,可以將每個矩形再分成小矩形,分別用來表示每個數據文件,相應地還可以繼續再分成更小的矩形,分別用來表示已用的或自由的區域(extent)。我們還要編寫一個小程序來顯示 SGA 分配和使用統計情況。 為了生成用于產生圖表的數據,我需要 DBA_DATA_FILES(里面有已分配的總空間數)和DBA_FREE_SPACE.我可以通過拿“總字節數”減去“已用字節數”來計算“自由字節數”,從而避免查詢自由 extent 表。這個視圖的 USER_ 和 ALL_ 版本不能查看到所有的數據,但是我不想將 DBA 權限給那些想得到這些數據的人。所以我拆開了 DNA_DATA_FILES 和 DBA_FREE_SPACE,從中先刪除了不相關的表,然后將它們合并到了一個單獨的視圖中,使用該視圖顯示已用的空間(自由空間為“0”)和自由空間(已用空間為“0”)。這樣就提高了查詢速度,而且通過將這個視圖授權給 PUBLIC,任何數據庫用戶都可以訪問其數據。下面是我的視圖定義,數據庫版本為 Oracle 9.2.0.1.0。create or replace view treemap_data_viewasselectts.nametablespace_name, 0 free_bytes, ts.blocksize * f.blocksused_bytes, ts.blocksize * f.blockstotal_bytes fromsys.file$ f, sys.ts$ ts where f.spare1 is null andf.ts# = ts.ts#union allselectts.name, 0, decode(hc.ktfbhccval,0,ts.blocksize * hc.ktfbhcsz,null), decode(hc.ktfbhccval,0,ts.blocksize * hc.ktfbhcsz,null) fromsys.ts$ ts, sys.x$ktfbhchcwherehc.ktfbhctsn = ts.ts#union allselectts.name, f.length * ts.blocksize, 0, f.length * ts.blocksizefromsys.ts$ ts, sys.fet$ fwherets.ts# = f.ts# andts.bitmapped = 0
union allselectts.name, f.ktfbfeblks * ts.blocksize, 0, f.ktfbfeblks * ts.blocksizefromsys.ts$ ts, sys.x$ktfbfe fwherets.ts# = f.ktfbfetsn andts.bitmapped <> 0 and ts.online$ in (1,4) and ts.contents$ = 0; grant select on treemap_data_view to public; 剩下的工作就是用PHP 文件來產生圖表。該文件接受寬度和長度參數來調整被約束的區域的大小。我為圖像分配了一個顏色表,顏色0是背景色,顏色1是文本和邊界顏色,顏色2-128是綠色和黃色之間的顏色,顏色129-256是黃色和紅色之間的顏色。 函數gendate將數據從數據庫裝入到一個本地表中。函數ImageCenterString在矩形的中心畫一些文本(表空間的名字)。 函數TreemapDrawGroup畫treemap矩形,使用相應的顏色填充矩形,并在矩形上寫名字。 TreemapGroup函數遞歸地將數據分成交互的垂直和水平分割,劃分的依據是每一邊的大小,即相對于表空間的大小的百分比。數據首先填入一個數組然后使用 PHP 函數“array_sum()”將兩邊相加得出總的大小。下面是 PHP 代碼: <?php// send headerHeader("Content-Type: image/png"); // look$font = 4;$fontwidth = ImageFontWidth($font);$fontheight = ImageFontHeight($font);// the query$username = 'scott';$passWord = 'tiger';$sql = <<<SQLselecttablespace_name, sum(free_bytes), sum(used_bytes), sum(total_bytes) fromtreemap_data_view group by tablespace_nameSQL; // where tablespace_name in ('SYSTEM','EJALA','TOOLS')// generate treemap datafunctiongendata(){ global $username,$password,$sql; // arrange this way to take advantage of "array_sum" $tmdata = array ( 'name'=>array(), 'freebytes'=>array(), 'usedbytes'=>array(), 'totalbytes'=>array() ); $conn = ora_logon($username,$password); ora_commitoff($conn); $cursor = ora_open($conn); ora_parse($cursor,$sql); ora_exec($cursor); $i = 0; while (ora_fetch($cursor)) { $tmdata['name'][$i] = trim(ora_getcolumn($cursor,0)); $tmdata['freebytes'][$i] = ora_getcolumn($cursor,1);
$tmdata['usedbytes'][$i] = ora_getcolumn($cursor,2); $tmdata['totalbytes'][$i] = ora_getcolumn($cursor,3); $i++; } ora_close($cursor); ora_logoff($conn); return $tmdata;} // draw text centered in a rectanglefunctionImageCenterString(&$image,$x,$y,$wd,$ht,$string){ global $font,$fontwidth,$fontheight; $tw = $fontwidth * strlen($string); $cx = ($tw > $wd) ?0 : ($wd - $tw) >> 1; $cy = ($fontheight > $ht) ?0 : ($ht - $fontheight) >> 1; ImageString($image,$font,$x+$cx,$y+$cy,$string,1);} functionTreemapDrawGroup(&$image,$x,$y,$wd,$ht,&$tmdata,$i){ $pctfree = $tmdata['freebytes'][$i]/$tmdata['totalbytes'][$i]; $color = round($pctfree * 254) + 2.0; ImageFilledRectangle($image,$x+1,$y+1,$x+$wd-1,$y+$ht-1,$color); ImageCenterString($image,$x,$y,$wd,$ht,$tmdata['name'][$i]);} functionTreemapGroup($lvl,&$image,$x,$y,$wd,$ht,&$tmdata){ $cnt = count($tmdata['name']); switch ($cnt) { case 1: // use entire area for rectangle TreemapDrawGroup($image,$x,$y,$wd,$ht,$tmdata,0); break; case 2: // draw split region $pct = $tmdata['totalbytes'][0] / ($tmdata['totalbytes'][0] + $tmdata['totalbytes'][1]); if ($lvl % 2) { // odd = vertical split $ht1 = $ht * $pct; TreemapDrawGroup($image,$x,$y,$wd,$ht1,$tmdata,0); ImageLine($image,$x,$y+$ht1,$x+$wd,$y+$ht1,1); TreemapDrawGroup($image,$x,$y+$ht1,$wd,$ht-$ht1,$tmdata,1); } else {
/// even = horizontal split $wd1 = $wd * $pct; TreemapDrawGroup($image,$x,$y,$wd1,$ht,$tmdata,0); ImageLine($image,$x+$wd1,$y,$x+$wd1,$y+$ht,1); TreemapDrawGroup($image,$x+$wd1,$y,$wd-$wd1,$ht,$tmdata,1); } break; default: // recursively pivot slices $s = $cnt >> 1; // determine split point $tm1 = array ( 'name' => array_slice($tmdata['name'],0,$s), 'freebytes' => array_slice($tmdata['freebytes'],0,$s), 'usedbytes' => array_slice($tmdata['usedbytes'],0,$s), 'totalbytes' => array_slice($tmdata['totalbytes'],0,$s) ); $tm2 = array ( 'name' => array_slice($tmdata['name'],$s), 'freebytes' => array_slice($tmdata['freebytes'],$s), 'usedbytes' => array_slice($tmdata['usedbytes'],$s), 'totalbytes' => array_slice($tmdata['totalbytes'],$s) ); $tm1sum = (float)array_sum($tm1['totalbytes']); $tm2sum = (float)array_sum($tm2['totalbytes']); $pct = $tm1sum / ($tm1sum + $tm2sum); if ($lvl % 2) { // odd = vertical split $ht1 = $ht * $pct;
TreemapGroup($lvl+1,$image,$x,$y,$wd,$ht1,$tm1); ImageLine($image,$x,$y+$ht1,$x+$wd,$y+$ht1,1); TreemapGroup($lvl+1,$image,$x,$y+$ht1,$wd,$ht-$ht1,$tm2); } else { // even = horizontal split $wd1 = $wd * $pct; TreemapGroup($lvl+1,$image,$x,$y,$wd1,$ht,$tm1); ImageLine($image,$x+$wd1,$y,$x+$wd1,$y+$ht,1); TreemapGroup($lvl+1,$image,$x+$wd1,$y,$wd-$wd1,$ht,$tm2); } break; }} // create image and colors$wd = ($_REQUEST["width"]) ? $_REQUEST["width"] : 640;$ht = ($_REQUEST["height"]) ? $_REQUEST["height"] : 480;$im = ImageCreate($wd,$ht);// allocate colorsImageColorAllocate($im,255,255,255); // background = whiteImageColorAllocate($im,0,0,0); // text = black// allocates 254 shades from green -> yello -> redfor ($i=0;$i<126;$i++) { ImageColorAllocate($im,$i*2,255,0); }for ($i=0;$i<126;$i++) { ImageColorAllocate($im,255,255-($i*2),0); } $tmdata = gendata();TreemapGroup(0,$im,0,0,$wd,$ht,$tmdata);// ImageCenterString($im,0,0,$wd,$ht,"count = ".count($tmdata['name'])); // draw border at edges of imageImageLine($im,0,0,0,$ht-1,1);ImageLine($im,0,$ht-1,$wd-1,$ht-1,1);ImageLine($im,$wd-1,$ht-1,$wd-1,0,1);ImageLine($im,$wd-1,0,0,0,1); // generate image to outputImagePNG($im);ImageDestroy($im);?>本文作者:Scott Stephens已經在Oracle工作了13年有余,他曾經在技術支持、電子商務、市場和軟件開發等部門工作。