github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/sink/observer/observer.go (about) 1 // Copyright 2023 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package observer 15 16 import ( 17 "context" 18 "net/url" 19 "strings" 20 "sync" 21 22 "github.com/pingcap/tiflow/cdc/model" 23 "github.com/pingcap/tiflow/pkg/config" 24 "github.com/pingcap/tiflow/pkg/errors" 25 cerror "github.com/pingcap/tiflow/pkg/errors" 26 "github.com/pingcap/tiflow/pkg/sink" 27 pmysql "github.com/pingcap/tiflow/pkg/sink/mysql" 28 ) 29 30 // Observer defines an interface of downstream performance observer. 31 type Observer interface { 32 // Tick is called periodically, Observer fetches performance metrics and 33 // records them in each Tick. 34 // Tick and Close must be concurrent safe. 35 Tick(ctx context.Context) error 36 Close() error 37 } 38 39 // NewObserverOpt represents available options when creating a new observer. 40 type NewObserverOpt struct { 41 dbConnFactory pmysql.Factory 42 } 43 44 // NewObserverOption configures NewObserverOpt. 45 type NewObserverOption func(*NewObserverOpt) 46 47 // WithDBConnFactory specifies factory to create db connection. 48 func WithDBConnFactory(factory pmysql.Factory) NewObserverOption { 49 return func(opt *NewObserverOpt) { 50 opt.dbConnFactory = factory 51 } 52 } 53 54 // NewObserver creates a new Observer 55 func NewObserver( 56 ctx context.Context, 57 changefeedID model.ChangeFeedID, 58 sinkURIStr string, 59 replCfg *config.ReplicaConfig, 60 opts ...NewObserverOption, 61 ) (Observer, error) { 62 creator := func() (Observer, error) { 63 options := &NewObserverOpt{dbConnFactory: pmysql.CreateMySQLDBConn} 64 for _, opt := range opts { 65 opt(options) 66 } 67 68 sinkURI, err := url.Parse(sinkURIStr) 69 if err != nil { 70 return nil, cerror.WrapError(cerror.ErrSinkURIInvalid, err) 71 } 72 73 scheme := strings.ToLower(sinkURI.Scheme) 74 if !sink.IsMySQLCompatibleScheme(scheme) { 75 return NewDummyObserver(), nil 76 } 77 78 cfg := pmysql.NewConfig() 79 err = cfg.Apply(config.GetGlobalServerConfig().TZ, changefeedID, sinkURI, replCfg) 80 if err != nil { 81 return nil, err 82 } 83 84 dsnStr, err := pmysql.GenerateDSN(ctx, sinkURI, cfg, options.dbConnFactory) 85 if err != nil { 86 return nil, err 87 } 88 db, err := options.dbConnFactory(ctx, dsnStr) 89 if err != nil { 90 return nil, err 91 } 92 db.SetMaxIdleConns(2) 93 db.SetMaxOpenConns(2) 94 95 isTiDB, err := pmysql.CheckIsTiDB(ctx, db) 96 if err != nil { 97 return nil, err 98 } 99 if isTiDB { 100 return NewTiDBObserver(db), nil 101 } 102 _ = db.Close() 103 return NewDummyObserver(), nil 104 } 105 return &observerAgent{creator: creator}, nil 106 } 107 108 type observerAgent struct { 109 creator func() (Observer, error) 110 111 mu struct { 112 sync.Mutex 113 inner Observer 114 closed bool 115 } 116 } 117 118 // Tick implements Observer interface. 119 func (o *observerAgent) Tick(ctx context.Context) error { 120 o.mu.Lock() 121 if o.mu.inner != nil { 122 defer o.mu.Unlock() 123 return o.mu.inner.Tick(ctx) 124 } 125 if o.mu.closed { 126 defer o.mu.Unlock() 127 return nil 128 } 129 o.mu.Unlock() 130 131 inner, err := o.creator() 132 if err != nil { 133 return errors.Trace(err) 134 } 135 136 o.mu.Lock() 137 defer o.mu.Unlock() 138 if !o.mu.closed { 139 o.mu.inner = inner 140 return o.mu.inner.Tick(ctx) 141 } 142 return nil 143 } 144 145 // Close implements Observer interface. 146 func (o *observerAgent) Close() error { 147 o.mu.Lock() 148 defer o.mu.Unlock() 149 if o.mu.inner != nil { 150 o.mu.closed = true 151 return o.mu.inner.Close() 152 } 153 return nil 154 }