fragck.tcl 4.2 KB

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