OLD | NEW |
| (Empty) |
1 # Run this TCL script using "testfixture" to get a report that shows | |
2 # the sequence of database pages used by a particular table or index. | |
3 # This information is used for fragmentation analysis. | |
4 # | |
5 | |
6 # Get the name of the database to analyze | |
7 # | |
8 | |
9 if {[llength $argv]!=2} { | |
10 puts stderr "Usage: $argv0 database-name table-or-index-name" | |
11 exit 1 | |
12 } | |
13 set file_to_analyze [lindex $argv 0] | |
14 if {![file exists $file_to_analyze]} { | |
15 puts stderr "No such file: $file_to_analyze" | |
16 exit 1 | |
17 } | |
18 if {![file readable $file_to_analyze]} { | |
19 puts stderr "File is not readable: $file_to_analyze" | |
20 exit 1 | |
21 } | |
22 if {[file size $file_to_analyze]<512} { | |
23 puts stderr "Empty or malformed database: $file_to_analyze" | |
24 exit 1 | |
25 } | |
26 set objname [lindex $argv 1] | |
27 | |
28 # Open the database | |
29 # | |
30 sqlite3 db [lindex $argv 0] | |
31 set DB [btree_open [lindex $argv 0] 1000 0] | |
32 | |
33 # This proc is a wrapper around the btree_cursor_info command. The | |
34 # second argument is an open btree cursor returned by [btree_cursor]. | |
35 # The first argument is the name of an array variable that exists in | |
36 # the scope of the caller. If the third argument is non-zero, then | |
37 # info is returned for the page that lies $up entries upwards in the | |
38 # tree-structure. (i.e. $up==1 returns the parent page, $up==2 the | |
39 # grandparent etc.) | |
40 # | |
41 # The following entries in that array are filled in with information retrieved | |
42 # using [btree_cursor_info]: | |
43 # | |
44 # $arrayvar(page_no) = The page number | |
45 # $arrayvar(entry_no) = The entry number | |
46 # $arrayvar(page_entries) = Total number of entries on this page | |
47 # $arrayvar(cell_size) = Cell size (local payload + header) | |
48 # $arrayvar(page_freebytes) = Number of free bytes on this page | |
49 # $arrayvar(page_freeblocks) = Number of free blocks on the page | |
50 # $arrayvar(payload_bytes) = Total payload size (local + overflow) | |
51 # $arrayvar(header_bytes) = Header size in bytes | |
52 # $arrayvar(local_payload_bytes) = Local payload size | |
53 # $arrayvar(parent) = Parent page number | |
54 # | |
55 proc cursor_info {arrayvar csr {up 0}} { | |
56 upvar $arrayvar a | |
57 foreach [list a(page_no) \ | |
58 a(entry_no) \ | |
59 a(page_entries) \ | |
60 a(cell_size) \ | |
61 a(page_freebytes) \ | |
62 a(page_freeblocks) \ | |
63 a(payload_bytes) \ | |
64 a(header_bytes) \ | |
65 a(local_payload_bytes) \ | |
66 a(parent) \ | |
67 a(first_ovfl) ] [btree_cursor_info $csr $up] break | |
68 } | |
69 | |
70 # Determine the page-size of the database. This global variable is used | |
71 # throughout the script. | |
72 # | |
73 set pageSize [db eval {PRAGMA page_size}] | |
74 | |
75 # Find the root page of table or index to be analyzed. Also find out | |
76 # if the object is a table or an index. | |
77 # | |
78 if {$objname=="sqlite_master"} { | |
79 set rootpage 1 | |
80 set type table | |
81 } else { | |
82 db eval { | |
83 SELECT rootpage, type FROM sqlite_master | |
84 WHERE name=$objname | |
85 } break | |
86 if {![info exists rootpage]} { | |
87 puts stderr "no such table or index: $objname" | |
88 exit 1 | |
89 } | |
90 if {$type!="table" && $type!="index"} { | |
91 puts stderr "$objname is something other than a table or index" | |
92 exit 1 | |
93 } | |
94 if {![string is integer -strict $rootpage]} { | |
95 puts stderr "invalid root page for $objname: $rootpage" | |
96 exit 1 | |
97 } | |
98 } | |
99 | |
100 # The cursor $csr is pointing to an entry. Print out information | |
101 # about the page that $up levels above that page that contains | |
102 # the entry. If $up==0 use the page that contains the entry. | |
103 # | |
104 # If information about the page has been printed already, then | |
105 # this is a no-op. | |
106 # | |
107 proc page_info {csr up} { | |
108 global seen | |
109 cursor_info ci $csr $up | |
110 set pg $ci(page_no) | |
111 if {[info exists seen($pg)]} return | |
112 set seen($pg) 1 | |
113 | |
114 # Do parent pages first | |
115 # | |
116 if {$ci(parent)} { | |
117 page_info $csr [expr {$up+1}] | |
118 } | |
119 | |
120 # Find the depth of this page | |
121 # | |
122 set depth 1 | |
123 set i $up | |
124 while {$ci(parent)} { | |
125 incr i | |
126 incr depth | |
127 cursor_info ci $csr $i | |
128 } | |
129 | |
130 # print the results | |
131 # | |
132 puts [format {LEVEL %d: %6d} $depth $pg] | |
133 } | |
134 | |
135 | |
136 | |
137 | |
138 # Loop through the object and print out page numbers | |
139 # | |
140 set csr [btree_cursor $DB $rootpage 0] | |
141 for {btree_first $csr} {![btree_eof $csr]} {btree_next $csr} { | |
142 page_info $csr 0 | |
143 set i 1 | |
144 foreach pg [btree_ovfl_info $DB $csr] { | |
145 puts [format {OVFL %3d: %6d} $i $pg] | |
146 incr i | |
147 } | |
148 } | |
149 exit 0 | |
OLD | NEW |