vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/query_list.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package tabletserver 18 19 import ( 20 "context" 21 "html/template" 22 "sort" 23 "sync" 24 "time" 25 26 "vitess.io/vitess/go/streamlog" 27 "vitess.io/vitess/go/vt/callinfo" 28 "vitess.io/vitess/go/vt/sqlparser" 29 ) 30 31 // QueryDetail is a simple wrapper for Query, Context and a killable conn. 32 type QueryDetail struct { 33 ctx context.Context 34 conn killable 35 connID int64 36 start time.Time 37 } 38 39 type killable interface { 40 Current() string 41 ID() int64 42 Kill(message string, elapsed time.Duration) error 43 } 44 45 // NewQueryDetail creates a new QueryDetail 46 func NewQueryDetail(ctx context.Context, conn killable) *QueryDetail { 47 return &QueryDetail{ctx: ctx, conn: conn, connID: conn.ID(), start: time.Now()} 48 } 49 50 // QueryList holds a thread safe list of QueryDetails 51 type QueryList struct { 52 name string 53 54 mu sync.Mutex 55 // on reconnect connection id will get reused by a different connection. 56 // so have to maintain a list to compare with the actual connection. 57 // and remove appropriately. 58 queryDetails map[int64][]*QueryDetail 59 } 60 61 // NewQueryList creates a new QueryList 62 func NewQueryList(name string) *QueryList { 63 return &QueryList{ 64 name: name, 65 queryDetails: make(map[int64][]*QueryDetail), 66 } 67 } 68 69 // Add adds a QueryDetail to QueryList 70 func (ql *QueryList) Add(qd *QueryDetail) { 71 ql.mu.Lock() 72 defer ql.mu.Unlock() 73 qds, exists := ql.queryDetails[qd.connID] 74 if exists { 75 ql.queryDetails[qd.connID] = append(qds, qd) 76 } else { 77 ql.queryDetails[qd.connID] = []*QueryDetail{qd} 78 } 79 } 80 81 // Remove removes a QueryDetail from QueryList 82 func (ql *QueryList) Remove(qd *QueryDetail) { 83 ql.mu.Lock() 84 defer ql.mu.Unlock() 85 qds, exists := ql.queryDetails[qd.connID] 86 if !exists { 87 return 88 } 89 if len(qds) == 1 { 90 delete(ql.queryDetails, qd.connID) 91 return 92 } 93 for i, q := range qds { 94 // match with the actual connection ID. 95 if q.conn.ID() == qd.conn.ID() { 96 ql.queryDetails[qd.connID] = append(qds[:i], qds[i+1:]...) 97 return 98 } 99 } 100 } 101 102 // Terminate updates the query status and kills the connection 103 func (ql *QueryList) Terminate(connID int64) bool { 104 ql.mu.Lock() 105 defer ql.mu.Unlock() 106 qds, exists := ql.queryDetails[connID] 107 if !exists { 108 return false 109 } 110 for _, qd := range qds { 111 _ = qd.conn.Kill("QueryList.Terminate()", time.Since(qd.start)) 112 } 113 return true 114 } 115 116 // TerminateAll terminates all queries and kills the MySQL connections 117 func (ql *QueryList) TerminateAll() { 118 ql.mu.Lock() 119 defer ql.mu.Unlock() 120 for _, qds := range ql.queryDetails { 121 for _, qd := range qds { 122 _ = qd.conn.Kill("QueryList.TerminateAll()", time.Since(qd.start)) 123 } 124 } 125 } 126 127 // QueryDetailzRow is used for rendering QueryDetail in a template 128 type QueryDetailzRow struct { 129 Type string 130 Query string 131 ContextHTML template.HTML 132 Start time.Time 133 Duration time.Duration 134 ConnID int64 135 State string 136 ShowTerminateLink bool 137 } 138 139 type byStartTime []QueryDetailzRow 140 141 func (a byStartTime) Len() int { return len(a) } 142 func (a byStartTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 143 func (a byStartTime) Less(i, j int) bool { return a[i].Start.Before(a[j].Start) } 144 145 // AppendQueryzRows returns a list of QueryDetailzRow sorted by start time 146 func (ql *QueryList) AppendQueryzRows(rows []QueryDetailzRow) []QueryDetailzRow { 147 ql.mu.Lock() 148 for _, qds := range ql.queryDetails { 149 for _, qd := range qds { 150 query := qd.conn.Current() 151 if streamlog.GetRedactDebugUIQueries() { 152 query, _ = sqlparser.RedactSQLQuery(query) 153 } 154 row := QueryDetailzRow{ 155 Type: ql.name, 156 Query: query, 157 ContextHTML: callinfo.HTMLFromContext(qd.ctx), 158 Start: qd.start, 159 Duration: time.Since(qd.start), 160 ConnID: qd.connID, 161 } 162 rows = append(rows, row) 163 } 164 } 165 ql.mu.Unlock() 166 sort.Sort(byStartTime(rows)) 167 return rows 168 }