|
| 1 | +// Copyright 2024 Block, Inc. |
| 2 | + |
| 3 | +package autoinc |
| 4 | + |
| 5 | +import ( |
| 6 | + "context" |
| 7 | + "database/sql" |
| 8 | + "strings" |
| 9 | + |
| 10 | + "github.com/cashapp/blip" |
| 11 | +) |
| 12 | + |
| 13 | +const ( |
| 14 | + DOMAIN = "autoinc" |
| 15 | + |
| 16 | + OPT_EXCLUDE = "exclude" |
| 17 | + OPT_INCLUDE = "include" |
| 18 | +) |
| 19 | + |
| 20 | +// AutoInc collects auto-increment utilization for the autoinc domain. |
| 21 | +// https://dev.mysql.com/doc/refman/8.0/en/sys-schema-auto-increment-columns.html |
| 22 | +type AutoInc struct { |
| 23 | + db *sql.DB |
| 24 | + // -- |
| 25 | + query map[string]string |
| 26 | + params map[string][]interface{} |
| 27 | +} |
| 28 | + |
| 29 | +// Verify collector implements blip.Collector interface. |
| 30 | +var _ blip.Collector = &AutoInc{} |
| 31 | + |
| 32 | +// NewAutoIncrement makes a new AutoIncrement collector, |
| 33 | +func NewAutoInc(db *sql.DB) *AutoInc { |
| 34 | + return &AutoInc{ |
| 35 | + db: db, |
| 36 | + query: map[string]string{}, |
| 37 | + params: map[string][]interface{}{}, |
| 38 | + } |
| 39 | +} |
| 40 | + |
| 41 | +// Domain returns the Blip metric domain name (DOMAIN const). |
| 42 | +func (t *AutoInc) Domain() string { |
| 43 | + return DOMAIN |
| 44 | +} |
| 45 | + |
| 46 | +// Help returns the output for blip --print-domains. |
| 47 | +func (t *AutoInc) Help() blip.CollectorHelp { |
| 48 | + return blip.CollectorHelp{ |
| 49 | + Domain: DOMAIN, |
| 50 | + Description: "Auto Increment Utilization", |
| 51 | + Options: map[string]blip.CollectorHelpOption{ |
| 52 | + OPT_INCLUDE: { |
| 53 | + Name: OPT_INCLUDE, |
| 54 | + Desc: "Comma-separated list of database or table names to include (overrides option " + OPT_EXCLUDE + ")", |
| 55 | + }, |
| 56 | + OPT_EXCLUDE: { |
| 57 | + Name: OPT_EXCLUDE, |
| 58 | + Desc: "Comma-separated list of database or table names to exclude (ignored if " + OPT_INCLUDE + " is set)", |
| 59 | + Default: "mysql.*,information_schema.*,performance_schema.*,sys.*", |
| 60 | + }, |
| 61 | + }, |
| 62 | + Groups: []blip.CollectorKeyValue{ |
| 63 | + {Key: "db", Value: "the database name for the corresponding auto increment value"}, |
| 64 | + {Key: "tbl", Value: "the table name for the corresponding auto increment value"}, |
| 65 | + {Key: "col", Value: "the column name for the corresponding auto increment value"}, |
| 66 | + {Key: "data_type", Value: "the data type of the column"}, |
| 67 | + }, |
| 68 | + Metrics: []blip.CollectorMetric{ |
| 69 | + { |
| 70 | + Name: "usage", |
| 71 | + Type: blip.GAUGE, |
| 72 | + Desc: "The percentage of the auto increment range used", |
| 73 | + }, |
| 74 | + }, |
| 75 | + } |
| 76 | +} |
| 77 | + |
| 78 | +// Prepare prepares the collector for the given plan. |
| 79 | +func (t *AutoInc) Prepare(ctx context.Context, plan blip.Plan) (func(), error) { |
| 80 | +LEVEL: |
| 81 | + for _, level := range plan.Levels { |
| 82 | + dom, ok := level.Collect[DOMAIN] |
| 83 | + if !ok { |
| 84 | + continue LEVEL // not collected in this level |
| 85 | + } |
| 86 | + if dom.Options == nil { |
| 87 | + dom.Options = make(map[string]string) |
| 88 | + } |
| 89 | + if _, ok := dom.Options[OPT_EXCLUDE]; !ok { |
| 90 | + dom.Options[OPT_EXCLUDE] = "mysql.*,information_schema.*,performance_schema.*,sys.*" |
| 91 | + } |
| 92 | + |
| 93 | + q, params, err := AutoIncrementQuery(dom.Options) |
| 94 | + if err != nil { |
| 95 | + return nil, err |
| 96 | + } |
| 97 | + t.query[level.Name] = q |
| 98 | + t.params[level.Name] = params |
| 99 | + } |
| 100 | + return nil, nil |
| 101 | +} |
| 102 | + |
| 103 | +func (t *AutoInc) Collect(ctx context.Context, levelName string) ([]blip.MetricValue, error) { |
| 104 | + q, ok := t.query[levelName] |
| 105 | + if !ok { |
| 106 | + return nil, nil |
| 107 | + } |
| 108 | + |
| 109 | + rows, err := t.db.QueryContext(ctx, q, t.params[levelName]...) |
| 110 | + if err != nil { |
| 111 | + return nil, err |
| 112 | + } |
| 113 | + defer rows.Close() |
| 114 | + |
| 115 | + var ( |
| 116 | + metrics []blip.MetricValue |
| 117 | + dbName string |
| 118 | + tblName string |
| 119 | + colName string |
| 120 | + colType string |
| 121 | + isUnsigned bool |
| 122 | + autoincVal int64 |
| 123 | + ) |
| 124 | + |
| 125 | + for rows.Next() { |
| 126 | + if err = rows.Scan(&dbName, &tblName, &colName, &colType, &isUnsigned, &autoincVal); err != nil { |
| 127 | + return nil, err |
| 128 | + } |
| 129 | + |
| 130 | + var maxSize uint64 |
| 131 | + colType = strings.ToLower(colType) |
| 132 | + switch colType { |
| 133 | + case "tinyint": |
| 134 | + maxSize = 255 |
| 135 | + case "smallint": |
| 136 | + maxSize = 65535 |
| 137 | + case "mediumint": |
| 138 | + maxSize = 16777215 |
| 139 | + case "int": |
| 140 | + maxSize = 4294967295 |
| 141 | + case "bigint": |
| 142 | + maxSize = 18446744073709551615 |
| 143 | + default: |
| 144 | + // unknown type, skip |
| 145 | + continue |
| 146 | + } |
| 147 | + |
| 148 | + if !isUnsigned { |
| 149 | + maxSize = maxSize >> 1 |
| 150 | + } else { |
| 151 | + colType = colType + " unsigned" |
| 152 | + } |
| 153 | + |
| 154 | + m := blip.MetricValue{ |
| 155 | + Name: "usage", |
| 156 | + Type: blip.GAUGE, |
| 157 | + Group: map[string]string{"db": dbName, "tbl": tblName, "col": colName, "data_type": colType}, |
| 158 | + Value: float64(autoincVal) / float64(maxSize), |
| 159 | + } |
| 160 | + metrics = append(metrics, m) |
| 161 | + } |
| 162 | + |
| 163 | + return metrics, err |
| 164 | +} |
0 commit comments