master
go 199 lines 5.39 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package oracledb
4
5 import (
6 "fmt"
7 "strconv"
8 "strings"
9 )
10
11 const queryTablespace = `
12 SELECT
13 ts.tablespace_name,
14 ts.contents AS tablespace_type,
15 CASE
16 WHEN df_all.files_with_autoextend = 0 THEN 'NO'
17 WHEN df_all.files_with_autoextend = df_all.total_files THEN 'YES'
18 ELSE 'MIXED (' || df_all.files_with_autoextend || '/' || df_all.total_files || ')'
19 END AS autoextend_status,
20 CASE
21 -- For PERMANENT tablespaces: allocated - free
22 WHEN ts.contents = 'PERMANENT' THEN
23 COALESCE(df_all.allocated_bytes, 0) - COALESCE(fs.free_bytes, 0)
24 -- For UNDO tablespaces: sum of UNDO segments
25 WHEN ts.contents = 'UNDO' THEN
26 COALESCE(us.used_bytes, 0)
27 -- For TEMPORARY tablespaces: use v$temp_space_header
28 WHEN ts.contents = 'TEMPORARY' THEN
29 COALESCE(tu.used_bytes, 0)
30 ELSE 0
31 END AS used_bytes,
32 COALESCE(df_all.allocated_bytes, 0) AS allocated_bytes,
33 -- For max_bytes, only count maxbytes from files with autoextend=YES
34 COALESCE(df_all.max_bytes, 0) AS max_bytes
35 FROM
36 dba_tablespaces ts
37 -- Combined datafiles and tempfiles info with autoextend counts
38 LEFT JOIN (
39 SELECT
40 tablespace_name,
41 COUNT(*) AS total_files,
42 SUM(CASE WHEN autoextensible = 'YES' THEN 1 ELSE 0 END) AS files_with_autoextend,
43 SUM(bytes) AS allocated_bytes,
44 -- Only count maxbytes for files with autoextend enabled
45 SUM(CASE
46 WHEN autoextensible = 'YES' AND maxbytes > 0 THEN maxbytes
47 WHEN autoextensible = 'YES' AND maxbytes = 0 THEN bytes
48 ELSE bytes -- For non-autoextend files, current size is the max
49 END) AS max_bytes
50 FROM dba_data_files
51 GROUP BY tablespace_name
52 UNION ALL
53 SELECT
54 tablespace_name,
55 COUNT(*) AS total_files,
56 SUM(CASE WHEN autoextensible = 'YES' THEN 1 ELSE 0 END) AS files_with_autoextend,
57 SUM(bytes) AS allocated_bytes,
58 SUM(CASE
59 WHEN autoextensible = 'YES' AND maxbytes > 0 THEN maxbytes
60 WHEN autoextensible = 'YES' AND maxbytes = 0 THEN bytes
61 ELSE bytes
62 END) AS max_bytes
63 FROM dba_temp_files
64 GROUP BY tablespace_name
65 ) df_all ON ts.tablespace_name = df_all.tablespace_name
66 -- Free space for permanent tablespaces
67 LEFT JOIN (
68 SELECT
69 tablespace_name,
70 SUM(bytes) AS free_bytes
71 FROM dba_free_space
72 GROUP BY tablespace_name
73 ) fs ON ts.tablespace_name = fs.tablespace_name
74 -- Used space for UNDO tablespaces
75 LEFT JOIN (
76 SELECT
77 tablespace_name,
78 SUM(bytes) AS used_bytes
79 FROM dba_segments
80 WHERE segment_type LIKE 'UNDO%'
81 GROUP BY tablespace_name
82 ) us ON ts.tablespace_name = us.tablespace_name
83 -- Used space for TEMPORARY tablespaces
84 LEFT JOIN (
85 SELECT
86 tablespace_name,
87 SUM(bytes_used) AS used_bytes
88 FROM v$temp_space_header
89 GROUP BY tablespace_name
90 ) tu ON ts.tablespace_name = tu.tablespace_name
91 WHERE
92 df_all.tablespace_name IS NOT NULL -- Only show tablespaces with datafiles/tempfiles
93 ORDER BY
94 ts.tablespace_name
95 `
96
97 type tablespaceInfo struct {
98 name string
99 typ string
100 autoExtent string
101 allocBytes float64
102 maxBytes float64
103 usedBytes float64
104 }
105
106 func (c *Collector) collectTablespace(mx map[string]int64) error {
107 q := queryTablespace
108 c.Debugf("executing query: %s", q)
109
110 var ts tablespaceInfo
111
112 seen := make(map[string]tablespaceInfo)
113
114 err := c.doQuery(q, func(column, value string, lineEnd bool) error {
115 var err error
116
117 switch column {
118 case "TABLESPACE_NAME":
119 ts.name = value
120 case "TABLESPACE_TYPE":
121 ts.typ = value
122 case "AUTOEXTEND_STATUS":
123 switch {
124 case value == "YES":
125 value = "enabled"
126 case value == "NO":
127 value = "disabled"
128 case strings.HasPrefix(value, "MIXED"):
129 value = "mixed"
130 }
131 ts.autoExtent = value
132 case "ALLOCATED_BYTES":
133 ts.allocBytes, err = strconv.ParseFloat(value, 64)
134 case "MAX_BYTES":
135 ts.maxBytes, err = strconv.ParseFloat(value, 64)
136 case "USED_BYTES":
137 ts.usedBytes, err = strconv.ParseFloat(value, 64)
138 }
139 if err != nil {
140 return fmt.Errorf("could not parse column '%s' value '%s': %w", column, value, err)
141 }
142
143 if lineEnd {
144 if ts.typ == "TEMPORARY" {
145 return nil
146 }
147 seen[ts.name] = ts
148
149 limit := ts.maxBytes
150 if ts.autoExtent == "disabled" {
151 limit = ts.allocBytes
152 }
153
154 used := ts.usedBytes
155 if used > limit {
156 used = limit
157 }
158
159 avail := limit - used
160
161 var util float64
162 if limit > 0 {
163 if util = used / limit * 100; util > 100 {
164 util = 100
165 }
166 }
167
168 px := fmt.Sprintf("tablespace_%s_", ts.name)
169
170 mx[px+"max_size_bytes"] = int64(limit)
171 mx[px+"used_bytes"] = int64(used)
172 mx[px+"avail_bytes"] = int64(avail)
173 mx[px+"utilization"] = 0
174 if limit > 0 {
175 mx[px+"utilization"] = int64(util * precision)
176 }
177 }
178
179 return nil
180 })
181 if err != nil {
182 return err
183 }
184
185 for _, ts := range seen {
186 if !c.seenTablespaces[ts.name] {
187 c.seenTablespaces[ts.name] = true
188 c.addTablespaceCharts(ts)
189 }
190 }
191 for name := range c.seenTablespaces {
192 if _, ok := seen[name]; !ok {
193 delete(c.seenTablespaces, name)
194 c.removeTablespaceChart(name)
195 }
196 }
197
198 return nil
199 }