github.com/m3db/m3@v1.5.0/src/aggregator/client/m3msg_client.go (about) 1 // Copyright (c) 2020 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package client 22 23 import ( 24 "fmt" 25 "sync" 26 27 "github.com/uber-go/tally" 28 "go.uber.org/zap" 29 30 "github.com/m3db/m3/src/aggregator/sharding" 31 "github.com/m3db/m3/src/metrics/generated/proto/metricpb" 32 "github.com/m3db/m3/src/metrics/metadata" 33 "github.com/m3db/m3/src/metrics/metric" 34 "github.com/m3db/m3/src/metrics/metric/aggregated" 35 "github.com/m3db/m3/src/metrics/metric/id" 36 "github.com/m3db/m3/src/metrics/metric/unaggregated" 37 "github.com/m3db/m3/src/metrics/policy" 38 "github.com/m3db/m3/src/msg/producer" 39 "github.com/m3db/m3/src/x/clock" 40 "github.com/m3db/m3/src/x/instrument" 41 ) 42 43 var _ AdminClient = (*M3MsgClient)(nil) 44 45 // M3MsgClient sends metrics to M3 Aggregator over m3msg. 46 type M3MsgClient struct { 47 m3msg m3msgClient 48 nowFn clock.NowFn 49 shardFn sharding.ShardFn 50 metrics m3msgClientMetrics 51 } 52 53 type m3msgClient struct { 54 producer producer.Producer 55 numShards uint32 56 messagePool *messagePool 57 } 58 59 // NewM3MsgClient creates a new M3 Aggregator client that uses M3Msg. 60 func NewM3MsgClient(opts Options) (Client, error) { 61 if err := opts.Validate(); err != nil { 62 return nil, err 63 } 64 65 m3msgOpts := opts.M3MsgOptions() 66 if err := m3msgOpts.Validate(); err != nil { 67 return nil, err 68 } 69 70 producer := m3msgOpts.Producer() 71 if err := producer.Init(); err != nil { 72 return nil, err 73 } 74 75 msgClient := m3msgClient{ 76 producer: producer, 77 numShards: producer.NumShards(), 78 messagePool: newMessagePool(), 79 } 80 81 var ( 82 iOpts = opts.InstrumentOptions() 83 logger = iOpts.Logger() 84 ) 85 86 logger.Info("creating M3MsgClient", zap.Uint32("numShards", msgClient.numShards)) 87 88 return &M3MsgClient{ 89 m3msg: msgClient, 90 nowFn: opts.ClockOptions().NowFn(), 91 shardFn: opts.ShardFn(), 92 metrics: newM3msgClientMetrics(iOpts.MetricsScope(), iOpts.TimerOptions()), 93 }, nil 94 } 95 96 // Init just satisfies Client interface, M3Msg client does not need explicit initialization. 97 func (c *M3MsgClient) Init() error { 98 return nil 99 } 100 101 // WriteUntimedCounter writes untimed counter metrics. 102 func (c *M3MsgClient) WriteUntimedCounter( 103 counter unaggregated.Counter, 104 metadatas metadata.StagedMetadatas, 105 ) error { 106 callStart := c.nowFn() 107 payload := payloadUnion{ 108 payloadType: untimedType, 109 untimed: untimedPayload{ 110 metric: counter.ToUnion(), 111 metadatas: metadatas, 112 }, 113 } 114 err := c.write(counter.ID, payload) 115 c.metrics.writeUntimedCounter.ReportSuccessOrError(err, c.nowFn().Sub(callStart)) 116 return err 117 } 118 119 // WriteUntimedBatchTimer writes untimed batch timer metrics. 120 func (c *M3MsgClient) WriteUntimedBatchTimer( 121 batchTimer unaggregated.BatchTimer, 122 metadatas metadata.StagedMetadatas, 123 ) error { 124 callStart := c.nowFn() 125 payload := payloadUnion{ 126 payloadType: untimedType, 127 untimed: untimedPayload{ 128 metric: batchTimer.ToUnion(), 129 metadatas: metadatas, 130 }, 131 } 132 err := c.write(batchTimer.ID, payload) 133 c.metrics.writeUntimedBatchTimer.ReportSuccessOrError(err, c.nowFn().Sub(callStart)) 134 return err 135 } 136 137 // WriteUntimedGauge writes untimed gauge metrics. 138 func (c *M3MsgClient) WriteUntimedGauge( 139 gauge unaggregated.Gauge, 140 metadatas metadata.StagedMetadatas, 141 ) error { 142 callStart := c.nowFn() 143 payload := payloadUnion{ 144 payloadType: untimedType, 145 untimed: untimedPayload{ 146 metric: gauge.ToUnion(), 147 metadatas: metadatas, 148 }, 149 } 150 err := c.write(gauge.ID, payload) 151 c.metrics.writeUntimedGauge.ReportSuccessOrError(err, c.nowFn().Sub(callStart)) 152 return err 153 } 154 155 // WriteTimed writes timed metrics. 156 func (c *M3MsgClient) WriteTimed( 157 metric aggregated.Metric, 158 metadata metadata.TimedMetadata, 159 ) error { 160 callStart := c.nowFn() 161 payload := payloadUnion{ 162 payloadType: timedType, 163 timed: timedPayload{ 164 metric: metric, 165 metadata: metadata, 166 }, 167 } 168 err := c.write(metric.ID, payload) 169 c.metrics.writeForwarded.ReportSuccessOrError(err, c.nowFn().Sub(callStart)) 170 return err 171 } 172 173 // WritePassthrough writes passthrough metrics. 174 func (c *M3MsgClient) WritePassthrough( 175 metric aggregated.Metric, 176 storagePolicy policy.StoragePolicy, 177 ) error { 178 callStart := c.nowFn() 179 payload := payloadUnion{ 180 payloadType: passthroughType, 181 passthrough: passthroughPayload{ 182 metric: metric, 183 storagePolicy: storagePolicy, 184 }, 185 } 186 err := c.write(metric.ID, payload) 187 c.metrics.writePassthrough.ReportSuccessOrError(err, c.nowFn().Sub(callStart)) 188 return err 189 } 190 191 // WriteTimedWithStagedMetadatas writes timed metrics with staged metadatas. 192 func (c *M3MsgClient) WriteTimedWithStagedMetadatas( 193 metric aggregated.Metric, 194 metadatas metadata.StagedMetadatas, 195 ) error { 196 callStart := c.nowFn() 197 payload := payloadUnion{ 198 payloadType: timedWithStagedMetadatasType, 199 timedWithStagedMetadatas: timedWithStagedMetadatas{ 200 metric: metric, 201 metadatas: metadatas, 202 }, 203 } 204 err := c.write(metric.ID, payload) 205 c.metrics.writeForwarded.ReportSuccessOrError(err, c.nowFn().Sub(callStart)) 206 return err 207 } 208 209 // WriteForwarded writes forwarded metrics. 210 func (c *M3MsgClient) WriteForwarded( 211 metric aggregated.ForwardedMetric, 212 metadata metadata.ForwardMetadata, 213 ) error { 214 callStart := c.nowFn() 215 payload := payloadUnion{ 216 payloadType: forwardedType, 217 forwarded: forwardedPayload{ 218 metric: metric, 219 metadata: metadata, 220 }, 221 } 222 err := c.write(metric.ID, payload) 223 c.metrics.writeForwarded.ReportSuccessOrError(err, c.nowFn().Sub(callStart)) 224 return err 225 } 226 227 //nolint:gocritic 228 func (c *M3MsgClient) write(metricID id.RawID, payload payloadUnion) error { 229 shard := c.shardFn(metricID, c.m3msg.numShards) 230 231 msg := c.m3msg.messagePool.Get() 232 if err := msg.Encode(shard, payload); err != nil { 233 msg.Finalize(producer.Dropped) 234 return err 235 } 236 237 if err := c.m3msg.producer.Produce(msg); err != nil { 238 msg.Finalize(producer.Dropped) 239 return err 240 } 241 242 return nil 243 } 244 245 // Flush satisfies Client interface, as M3Msg client does not need explicit flushing. 246 func (c *M3MsgClient) Flush() error { 247 return nil 248 } 249 250 // Close closes the client. 251 func (c *M3MsgClient) Close() error { 252 c.m3msg.producer.Close(producer.WaitForConsumption) 253 return nil 254 } 255 256 type m3msgClientMetrics struct { 257 writeUntimedCounter instrument.MethodMetrics 258 writeUntimedBatchTimer instrument.MethodMetrics 259 writeUntimedGauge instrument.MethodMetrics 260 writePassthrough instrument.MethodMetrics 261 writeForwarded instrument.MethodMetrics 262 } 263 264 func newM3msgClientMetrics( 265 scope tally.Scope, 266 opts instrument.TimerOptions, 267 ) m3msgClientMetrics { 268 return m3msgClientMetrics{ 269 writeUntimedCounter: instrument.NewMethodMetrics(scope, "writeUntimedCounter", opts), 270 writeUntimedBatchTimer: instrument.NewMethodMetrics(scope, "writeUntimedBatchTimer", opts), 271 writeUntimedGauge: instrument.NewMethodMetrics(scope, "writeUntimedGauge", opts), 272 writePassthrough: instrument.NewMethodMetrics(scope, "writePassthrough", opts), 273 writeForwarded: instrument.NewMethodMetrics(scope, "writeForwarded", opts), 274 } 275 } 276 277 type messagePool struct { 278 pool sync.Pool 279 } 280 281 func newMessagePool() *messagePool { 282 p := &messagePool{} 283 p.pool.New = func() interface{} { 284 return newMessage(p) 285 } 286 return p 287 } 288 289 func (m *messagePool) Get() *message { 290 return m.pool.Get().(*message) 291 } 292 293 func (m *messagePool) Put(msg *message) { 294 m.pool.Put(msg) 295 } 296 297 // Ensure message implements m3msg producer message interface. 298 var _ producer.Message = (*message)(nil) 299 300 type message struct { 301 pool *messagePool 302 shard uint32 303 304 metric metricpb.MetricWithMetadatas 305 cm metricpb.CounterWithMetadatas 306 bm metricpb.BatchTimerWithMetadatas 307 gm metricpb.GaugeWithMetadatas 308 fm metricpb.ForwardedMetricWithMetadata 309 tm metricpb.TimedMetricWithMetadata 310 tms metricpb.TimedMetricWithMetadatas 311 312 buf []byte 313 } 314 315 func newMessage(pool *messagePool) *message { 316 return &message{ 317 pool: pool, 318 } 319 } 320 321 // Encode encodes a m3msg payload 322 //nolint:gocyclo,gocritic 323 func (m *message) Encode( 324 shard uint32, 325 payload payloadUnion, 326 ) error { 327 m.shard = shard 328 329 switch payload.payloadType { 330 case untimedType: 331 switch payload.untimed.metric.Type { 332 case metric.CounterType: 333 value := unaggregated.CounterWithMetadatas{ 334 Counter: payload.untimed.metric.Counter(), 335 StagedMetadatas: payload.untimed.metadatas, 336 } 337 if err := value.ToProto(&m.cm); err != nil { 338 return err 339 } 340 341 m.metric = metricpb.MetricWithMetadatas{ 342 Type: metricpb.MetricWithMetadatas_COUNTER_WITH_METADATAS, 343 CounterWithMetadatas: &m.cm, 344 } 345 case metric.TimerType: 346 value := unaggregated.BatchTimerWithMetadatas{ 347 BatchTimer: payload.untimed.metric.BatchTimer(), 348 StagedMetadatas: payload.untimed.metadatas, 349 } 350 if err := value.ToProto(&m.bm); err != nil { 351 return err 352 } 353 354 m.metric = metricpb.MetricWithMetadatas{ 355 Type: metricpb.MetricWithMetadatas_BATCH_TIMER_WITH_METADATAS, 356 BatchTimerWithMetadatas: &m.bm, 357 } 358 case metric.GaugeType: 359 value := unaggregated.GaugeWithMetadatas{ 360 Gauge: payload.untimed.metric.Gauge(), 361 StagedMetadatas: payload.untimed.metadatas, 362 } 363 if err := value.ToProto(&m.gm); err != nil { 364 return err 365 } 366 367 m.metric = metricpb.MetricWithMetadatas{ 368 Type: metricpb.MetricWithMetadatas_GAUGE_WITH_METADATAS, 369 GaugeWithMetadatas: &m.gm, 370 } 371 default: 372 return fmt.Errorf("unrecognized metric type: %v", 373 payload.untimed.metric.Type) 374 } 375 case forwardedType: 376 value := aggregated.ForwardedMetricWithMetadata{ 377 ForwardedMetric: payload.forwarded.metric, 378 ForwardMetadata: payload.forwarded.metadata, 379 } 380 if err := value.ToProto(&m.fm); err != nil { 381 return err 382 } 383 384 m.metric = metricpb.MetricWithMetadatas{ 385 Type: metricpb.MetricWithMetadatas_FORWARDED_METRIC_WITH_METADATA, 386 ForwardedMetricWithMetadata: &m.fm, 387 } 388 case timedType: 389 value := aggregated.TimedMetricWithMetadata{ 390 Metric: payload.timed.metric, 391 TimedMetadata: payload.timed.metadata, 392 } 393 if err := value.ToProto(&m.tm); err != nil { 394 return err 395 } 396 397 m.metric = metricpb.MetricWithMetadatas{ 398 Type: metricpb.MetricWithMetadatas_TIMED_METRIC_WITH_METADATA, 399 TimedMetricWithMetadata: &m.tm, 400 } 401 case timedWithStagedMetadatasType: 402 value := aggregated.TimedMetricWithMetadatas{ 403 Metric: payload.timedWithStagedMetadatas.metric, 404 StagedMetadatas: payload.timedWithStagedMetadatas.metadatas, 405 } 406 if err := value.ToProto(&m.tms); err != nil { 407 return err 408 } 409 410 m.metric = metricpb.MetricWithMetadatas{ 411 Type: metricpb.MetricWithMetadatas_TIMED_METRIC_WITH_METADATAS, 412 TimedMetricWithMetadatas: &m.tms, 413 } 414 default: 415 return fmt.Errorf("unrecognized payload type: %v", 416 payload.payloadType) 417 } 418 419 size := m.metric.Size() 420 if size > cap(m.buf) { 421 const growthFactor = 2 422 m.buf = make([]byte, int(growthFactor*float64(size))) 423 } 424 425 // Resize buffer to exactly how long we need for marshaling. 426 m.buf = m.buf[:size] 427 428 _, err := m.metric.MarshalTo(m.buf) 429 return err 430 } 431 432 func (m *message) Shard() uint32 { 433 return m.shard 434 } 435 436 func (m *message) Bytes() []byte { 437 return m.buf 438 } 439 440 func (m *message) Size() int { 441 return len(m.buf) 442 } 443 444 func (m *message) Finalize(reason producer.FinalizeReason) { 445 // Return to pool. 446 m.pool.Put(m) 447 }