statsd.go

时间:2021-08-31 12:02:05
package nsqd

import (
    "fmt"
    "math"
    "runtime"
    "sort"
    "time"

    "github.com/nsqio/nsq/internal/statsd"
)

type Uint64Slice []uint64

func (s Uint64Slice) Len() int {
    return len(s)
}

func (s Uint64Slice) Swap(i, j int) {
    s[i], s[j] = s[j], s[i]
}

func (s Uint64Slice) Less(i, j int) bool {
    return s[i] < s[j]
}

func (n *NSQD) statsdLoop() {
    var lastMemStats runtime.MemStats
    var lastStats []TopicStats
    ticker := time.NewTicker(n.getOpts().StatsdInterval)
    for {
        select {
        case <-n.exitChan:
            goto exit
        case <-ticker.C:
            client := statsd.NewClient(n.getOpts().StatsdAddress, n.getOpts().StatsdPrefix)
            err := client.CreateSocket()
            if err != nil {
                n.logf("ERROR: failed to create UDP socket to statsd(%s)", client)
                continue
            }

            n.logf("STATSD: pushing stats to %s", client)

            stats := n.GetStats()
            for _, topic := range stats {
                // try to find the topic in the last collection
                lastTopic := TopicStats{}
                for _, checkTopic := range lastStats {
                    if topic.TopicName == checkTopic.TopicName {
                        lastTopic = checkTopic
                        break
                    }
                }
                diff := topic.MessageCount - lastTopic.MessageCount
                stat := fmt.Sprintf("topic.%s.message_count", topic.TopicName)
                client.Incr(stat, int64(diff))

                stat = fmt.Sprintf("topic.%s.depth", topic.TopicName)
                client.Gauge(stat, topic.Depth)

                stat = fmt.Sprintf("topic.%s.backend_depth", topic.TopicName)
                client.Gauge(stat, topic.BackendDepth)

                for _, item := range topic.E2eProcessingLatency.Percentiles {
                    stat = fmt.Sprintf("topic.%s.e2e_processing_latency_%.0f", topic.TopicName, item["quantile"]*100.0)
                    // We can cast the value to int64 since a value of 1 is the
                    // minimum resolution we will have, so there is no loss of
                    // accuracy
                    client.Gauge(stat, int64(item["value"]))
                }

                for _, channel := range topic.Channels {
                    // try to find the channel in the last collection
                    lastChannel := ChannelStats{}
                    for _, checkChannel := range lastTopic.Channels {
                        if channel.ChannelName == checkChannel.ChannelName {
                            lastChannel = checkChannel
                            break
                        }
                    }
                    diff := channel.MessageCount - lastChannel.MessageCount
                    stat := fmt.Sprintf("topic.%s.channel.%s.message_count", topic.TopicName, channel.ChannelName)
                    client.Incr(stat, int64(diff))

                    stat = fmt.Sprintf("topic.%s.channel.%s.depth", topic.TopicName, channel.ChannelName)
                    client.Gauge(stat, channel.Depth)

                    stat = fmt.Sprintf("topic.%s.channel.%s.backend_depth", topic.TopicName, channel.ChannelName)
                    client.Gauge(stat, channel.BackendDepth)

                    stat = fmt.Sprintf("topic.%s.channel.%s.in_flight_count", topic.TopicName, channel.ChannelName)
                    client.Gauge(stat, int64(channel.InFlightCount))

                    stat = fmt.Sprintf("topic.%s.channel.%s.deferred_count", topic.TopicName, channel.ChannelName)
                    client.Gauge(stat, int64(channel.DeferredCount))

                    diff = channel.RequeueCount - lastChannel.RequeueCount
                    stat = fmt.Sprintf("topic.%s.channel.%s.requeue_count", topic.TopicName, channel.ChannelName)
                    client.Incr(stat, int64(diff))

                    diff = channel.TimeoutCount - lastChannel.TimeoutCount
                    stat = fmt.Sprintf("topic.%s.channel.%s.timeout_count", topic.TopicName, channel.ChannelName)
                    client.Incr(stat, int64(diff))

                    stat = fmt.Sprintf("topic.%s.channel.%s.clients", topic.TopicName, channel.ChannelName)
                    client.Gauge(stat, int64(len(channel.Clients)))

                    for _, item := range channel.E2eProcessingLatency.Percentiles {
                        stat = fmt.Sprintf("topic.%s.channel.%s.e2e_processing_latency_%.0f", topic.TopicName, channel.ChannelName, item["quantile"]*100.0)
                        client.Gauge(stat, int64(item["value"]))
                    }
                }
            }
            lastStats = stats

            if n.getOpts().StatsdMemStats {
                var memStats runtime.MemStats
                runtime.ReadMemStats(&memStats)

                // sort the GC pause array
                length := len(memStats.PauseNs)
                if int(memStats.NumGC) < length {
                    length = int(memStats.NumGC)
                }
                gcPauses := make(Uint64Slice, length)
                copy(gcPauses, memStats.PauseNs[:length])
                sort.Sort(gcPauses)

                client.Gauge("mem.heap_objects", int64(memStats.HeapObjects))
                client.Gauge("mem.heap_idle_bytes", int64(memStats.HeapIdle))
                client.Gauge("mem.heap_in_use_bytes", int64(memStats.HeapInuse))
                client.Gauge("mem.heap_released_bytes", int64(memStats.HeapReleased))
                client.Gauge("mem.gc_pause_usec_100", int64(percentile(100.0, gcPauses, len(gcPauses))/1000))
                client.Gauge("mem.gc_pause_usec_99", int64(percentile(99.0, gcPauses, len(gcPauses))/1000))
                client.Gauge("mem.gc_pause_usec_95", int64(percentile(95.0, gcPauses, len(gcPauses))/1000))
                client.Gauge("mem.next_gc_bytes", int64(memStats.NextGC))
                client.Incr("mem.gc_runs", int64(memStats.NumGC-lastMemStats.NumGC))

                lastMemStats = memStats
            }

            client.Close()
        }
    }

exit:
    ticker.Stop()
}

func percentile(perc float64, arr []uint64, length int) uint64 {
    if length == 0 {
        return 0
    }
    indexOfPerc := int(math.Floor(((perc / 100.0) * float64(length)) + 0.5))
    if indexOfPerc >= length {
        indexOfPerc = length - 1
    }
    return arr[indexOfPerc]
}

相关文章