github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/server/statement_diagnostics_requests.go (about)

     1  // Copyright 2020 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package server
    12  
    13  import (
    14  	"bytes"
    15  	"context"
    16  	"encoding/json"
    17  	"time"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/security"
    20  	"github.com/cockroachdb/cockroach/pkg/server/serverpb"
    21  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    22  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    23  	"github.com/cockroachdb/errors"
    24  )
    25  
    26  type stmtDiagnosticsRequest struct {
    27  	ID                     int
    28  	StatementFingerprint   string
    29  	Completed              bool
    30  	StatementDiagnosticsID int
    31  	RequestedAt            time.Time
    32  }
    33  
    34  type stmtDiagnostics struct {
    35  	ID                   int
    36  	StatementFingerprint string
    37  	Trace                string
    38  	CollectedAt          time.Time
    39  }
    40  
    41  func (request *stmtDiagnosticsRequest) toProto() serverpb.StatementDiagnosticsReport {
    42  	resp := serverpb.StatementDiagnosticsReport{
    43  		Id:                     int64(request.ID),
    44  		Completed:              request.Completed,
    45  		StatementFingerprint:   request.StatementFingerprint,
    46  		StatementDiagnosticsId: int64(request.StatementDiagnosticsID),
    47  		RequestedAt:            request.RequestedAt,
    48  	}
    49  	return resp
    50  }
    51  
    52  func (diagnostics *stmtDiagnostics) toProto() serverpb.StatementDiagnostics {
    53  	resp := serverpb.StatementDiagnostics{
    54  		Id:                   int64(diagnostics.ID),
    55  		StatementFingerprint: diagnostics.StatementFingerprint,
    56  		CollectedAt:          diagnostics.CollectedAt,
    57  		Trace:                diagnostics.Trace,
    58  	}
    59  	return resp
    60  }
    61  
    62  // CreateStatementDiagnosticsRequest creates a statement diagnostics
    63  // request in the `system.statement_diagnostics_requests` table
    64  // to trace the next query matching the provided fingerprint.
    65  func (s *statusServer) CreateStatementDiagnosticsReport(
    66  	ctx context.Context, req *serverpb.CreateStatementDiagnosticsReportRequest,
    67  ) (*serverpb.CreateStatementDiagnosticsReportResponse, error) {
    68  	if _, err := s.admin.requireAdminUser(ctx); err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	ctx = propagateGatewayMetadata(ctx)
    73  	ctx = s.AnnotateCtx(ctx)
    74  
    75  	response := &serverpb.CreateStatementDiagnosticsReportResponse{
    76  		Report: &serverpb.StatementDiagnosticsReport{},
    77  	}
    78  
    79  	err := s.stmtDiagnosticsRequester.InsertRequest(ctx, req.StatementFingerprint)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	response.Report.StatementFingerprint = req.StatementFingerprint
    85  	return response, nil
    86  }
    87  
    88  // StatementDiagnosticsRequests retrieves all of the statement
    89  // diagnostics requests in the `system.statement_diagnostics_requests` table.
    90  func (s *statusServer) StatementDiagnosticsRequests(
    91  	ctx context.Context, req *serverpb.StatementDiagnosticsReportsRequest,
    92  ) (*serverpb.StatementDiagnosticsReportsResponse, error) {
    93  	if _, err := s.admin.requireAdminUser(ctx); err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	ctx = propagateGatewayMetadata(ctx)
    98  	ctx = s.AnnotateCtx(ctx)
    99  
   100  	var err error
   101  
   102  	// TODO(davidh): Add pagination to this request.
   103  	rows, err := s.internalExecutor.QueryEx(ctx, "stmt-diag-get-all", nil, /* txn */
   104  		sqlbase.InternalExecutorSessionDataOverride{
   105  			User: security.RootUser,
   106  		},
   107  		`SELECT
   108  			id,
   109  			statement_fingerprint,
   110  			completed,
   111  			statement_diagnostics_id,
   112  			requested_at
   113  		FROM
   114  			system.statement_diagnostics_requests`)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  
   119  	requests := make([]stmtDiagnosticsRequest, len(rows))
   120  	for i, row := range rows {
   121  		id := int(*row[0].(*tree.DInt))
   122  		statementFingerprint := string(*row[1].(*tree.DString))
   123  		completed := bool(*row[2].(*tree.DBool))
   124  		req := stmtDiagnosticsRequest{
   125  			ID:                   id,
   126  			StatementFingerprint: statementFingerprint,
   127  			Completed:            completed,
   128  		}
   129  
   130  		if row[3] != tree.DNull {
   131  			sdi := int(*row[3].(*tree.DInt))
   132  			req.StatementDiagnosticsID = sdi
   133  		}
   134  
   135  		if requestedAt, ok := row[4].(*tree.DTimestampTZ); ok {
   136  			req.RequestedAt = requestedAt.Time
   137  		}
   138  
   139  		requests[i] = req
   140  	}
   141  
   142  	response := &serverpb.StatementDiagnosticsReportsResponse{
   143  		Reports: make([]serverpb.StatementDiagnosticsReport, len(requests)),
   144  	}
   145  
   146  	for i, request := range requests {
   147  		response.Reports[i] = request.toProto()
   148  	}
   149  	return response, nil
   150  }
   151  
   152  // StatementDiagnostics retrieves a statement diagnostics instance
   153  // identified by the given ID. These are in the
   154  // `system.statement_diagnostics` table.
   155  //
   156  // This is generated once the trace is completed on a request created
   157  // by the CreateStatementDiagnosticsRequest call and is linked to
   158  // the original request with its ID.
   159  func (s *statusServer) StatementDiagnostics(
   160  	ctx context.Context, req *serverpb.StatementDiagnosticsRequest,
   161  ) (*serverpb.StatementDiagnosticsResponse, error) {
   162  	if _, err := s.admin.requireAdminUser(ctx); err != nil {
   163  		return nil, err
   164  	}
   165  
   166  	ctx = propagateGatewayMetadata(ctx)
   167  	ctx = s.AnnotateCtx(ctx)
   168  
   169  	var err error
   170  	row, err := s.internalExecutor.QueryRowEx(ctx, "stmt-diag-get-one", nil, /* txn */
   171  		sqlbase.InternalExecutorSessionDataOverride{
   172  			User: security.RootUser,
   173  		},
   174  		`SELECT
   175  			id,
   176  			statement_fingerprint,
   177  			collected_at,
   178  			trace
   179  		FROM
   180  			system.statement_diagnostics
   181  		WHERE
   182  			id = $1`, req.StatementDiagnosticsId)
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  	if row == nil {
   187  		return nil, errors.Newf(
   188  			"requested a statement diagnostic (%d) that does not exist",
   189  			req.StatementDiagnosticsId,
   190  		)
   191  	}
   192  
   193  	diagnostics := stmtDiagnostics{
   194  		ID: int(req.StatementDiagnosticsId),
   195  	}
   196  
   197  	if statementFingerprint, ok := row[1].(*tree.DString); ok {
   198  		diagnostics.StatementFingerprint = statementFingerprint.String()
   199  	}
   200  
   201  	if collectedAt, ok := row[2].(*tree.DTimestampTZ); ok {
   202  		diagnostics.CollectedAt = collectedAt.Time
   203  	}
   204  
   205  	if traceJSON, ok := row[3].(*tree.DJSON); ok {
   206  		traceJSONString, err := traceJSON.AsText()
   207  		if err != nil {
   208  			return nil, err
   209  		}
   210  		var prettyJSON bytes.Buffer
   211  		if err := json.Indent(
   212  			&prettyJSON, []byte(*traceJSONString), "" /* prefix */, "\t" /* indent */); err != nil {
   213  			return nil, errors.Wrap(err, "failed to parse JSON")
   214  		}
   215  		diagnostics.Trace = prettyJSON.String()
   216  	}
   217  
   218  	diagnosticsProto := diagnostics.toProto()
   219  	response := &serverpb.StatementDiagnosticsResponse{
   220  		Diagnostics: &diagnosticsProto,
   221  	}
   222  
   223  	return response, nil
   224  }