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  }