blob: 26cef85467de2e059bfd47096bada04028b0ff25 [file] [log] [blame]
// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package runtimemetrics
import (
"math"
original_metrics "runtime/metrics"
"github.com/apache/skywalking-go/plugins/core/metrics"
)
//nolint
var nameReplacing = map[string]*meterInfo{
// GC counts
"/gc/cycles/automatic:gc-cycles": newMetricsGaugeReplaceInfo("instance_golang_gc_count_labeled", "type", "automatic"),
"/gc/cycles/forced:gc-cycles": newMetricsGaugeReplaceInfo("instance_golang_gc_count_labeled", "type", "forced"),
"/gc/cycles/total:gc-cycles": newMetricsGaugeReplaceInfo("instance_golang_gc_count_labeled", "type", "total"),
// Heap allocs
"/gc/heap/allocs:bytes": newMetricsGaugeReplaceInfo("instance_golang_heap_alloc_size"),
"/gc/heap/allocs:objects": newMetricsGaugeReplaceInfo("instance_golang_heap_alloc_objects"),
// Heap frees
"/gc/heap/frees:bytes": newMetricsGaugeReplaceInfo("instance_golang_heap_frees"),
"/gc/heap/frees:objects": newMetricsGaugeReplaceInfo("instance_golang_heap_frees_objects"),
// Memory heap
"/memory/classes/heap/free:bytes": newMetricsGaugeReplaceInfo("instance_golang_memory_heap_labeled", "type", "free"),
"/memory/classes/heap/objects:bytes": newMetricsGaugeReplaceInfo("instance_golang_memory_heap_labeled", "type", "objects"),
"/memory/classes/heap/released:bytes": newMetricsGaugeReplaceInfo("instance_golang_memory_heap_labeled", "type", "released"),
"/memory/classes/heap/stacks:bytes": newMetricsGaugeReplaceInfo("instance_golang_memory_heap_labeled", "type", "stacks"),
"/memory/classes/heap/unused:bytes": newMetricsGaugeReplaceInfo("instance_golang_memory_heap_labeled", "type", "unused"),
// Metadata mcache
"/memory/classes/metadata/mcache/free:bytes": newMetricsGaugeReplaceInfo("instance_golang_metadata_mcache_labeled", "type", "free"),
"/memory/classes/metadata/mcache/inuse:bytes": newMetricsGaugeReplaceInfo("instance_golang_metadata_mcache_labeled", "type", "inuse"),
// Metadata mspan
"/memory/classes/metadata/mspan/free:bytes": newMetricsGaugeReplaceInfo("instance_golang_metadata_mspan_labeled", "type", "free"),
"/memory/classes/metadata/mspan/inuse:bytes": newMetricsGaugeReplaceInfo("instance_golang_metadata_mspan_labeled", "type", "inuse"),
// threads
"/sched/gomaxprocs:threads": newMetricsGaugeReplaceInfo("instance_golang_os_threads_num"),
"/sched/goroutines:goroutines": newMetricsGaugeReplaceInfo("instance_golang_live_goroutines_num"),
// Others
"/cgo/go-to-c-calls:calls": newMetricsGaugeReplaceInfo("instance_golang_cgo_calls"),
"/gc/heap/goal:bytes": newMetricsGaugeReplaceInfo("instance_golang_gc_heap_goal"),
"/gc/heap/objects:objects": newMetricsGaugeReplaceInfo("instance_golang_gc_heap_objects"),
"/gc/heap/tiny/allocs:objects": newMetricsGaugeReplaceInfo("instance_golang_gc_heap_tiny_allocs"),
"/gc/limiter/last-enabled:gc-cycle": newMetricsGaugeReplaceInfo("instance_golang_gc_limiter_last_enabled"),
"/gc/stack/starting-size:bytes": newMetricsGaugeReplaceInfo("instance_golang_gc_stack_starting_size"),
"/memory/classes/metadata/other:bytes": newMetricsGaugeReplaceInfo("instance_golang_memory_metadata_other"),
"/memory/classes/os-stacks:bytes": newMetricsGaugeReplaceInfo("instance_golang_memory_os_stacks"),
"/memory/classes/other:bytes": newMetricsGaugeReplaceInfo("instance_golang_memory_other"),
"/memory/classes/profiling/buckets:bytes": newMetricsGaugeReplaceInfo("instance_golang_memory_profiling_buckets"),
"/memory/classes/total:bytes": newMetricsGaugeReplaceInfo("instance_golang_memory_total"),
// Histogram
"/gc/heap/allocs-by-size:bytes": newMetricsHistogramReplaceInfo("instance_golang_gc_heap_allocs_by_size", 1),
"/gc/heap/frees-by-size:bytes": newMetricsHistogramReplaceInfo("instance_golang_gc_heap_frees_by_size", 1),
"/gc/pauses:seconds": newMetricsHistogramReplaceInfo("instance_golang_gc_pauses", 1000_000_000),
"/sched/latencies:seconds": newMetricsHistogramReplaceInfo("instance_golang_sched_latencies", 1000_000_000),
}
//nolint
var combinedMetrics = []*meterInfo{
newCombinedGaugeInfo("instance_golang_memory_heap_labeled", []string{
"/memory/classes/heap/free:bytes", "/memory/classes/heap/objects:bytes", "/memory/classes/heap/released:bytes",
"/memory/classes/heap/stacks:bytes", "/memory/classes/heap/unused:bytes",
}, "type", "total"),
}
//nolint
//skywalking:init
func registerMetrics() {
allMetrics := original_metrics.All()
samples := make([]original_metrics.Sample, 0)
infos := make(map[string]*meterInfo)
combinedInfos := make([]*meterInfo, 0)
for _, m := range allMetrics {
info := nameReplacing[m.Name]
if info == nil {
continue
}
sample := original_metrics.Sample{Name: m.Name}
samples = append(samples, sample)
info.init(sample)
infos[m.Name] = info
}
for _, info := range combinedMetrics {
info.initWithCombined()
combinedInfos = append(combinedInfos, info)
}
metrics.RegisterBeforeCollectHook(func() {
// reading all samples
original_metrics.Read(samples)
// updating metrics
for _, sample := range samples {
if i := infos[sample.Name]; i != nil {
i.updateMetricValue(sample)
}
}
for _, info := range combinedInfos {
info.updateCombinedMetricValue(samples)
}
})
}
type meterInfo struct {
// basic info
name string
tagOpts []metrics.Opt
isHistogram bool
histogramMultiple int
// metric value
gaugeValue float64
gaugeMetric metrics.Gauge
latestHistogramValue []int64
histogramMetric metrics.Histogram
histogramStartInx int
// combined metrics
needsMetricsNames []string
combinedGaugeMetric metrics.Gauge
combinedGaugeValue float64
}
func newMetricsGaugeReplaceInfo(name string, tags ...string) *meterInfo {
meter := &meterInfo{name: name, isHistogram: false}
if len(tags) > 0 {
meter.tagOpts = make([]metrics.Opt, 0)
for i := 0; i < len(tags); i += 2 {
meter.tagOpts = append(meter.tagOpts, metrics.WithLabel(tags[i], tags[i+1]))
}
}
return meter
}
func newMetricsHistogramReplaceInfo(name string, multiples int) *meterInfo {
meter := &meterInfo{name: name, isHistogram: true, histogramMultiple: multiples}
return meter
}
func newCombinedGaugeInfo(name string, needsMetrics []string, tags ...string) *meterInfo {
meter := &meterInfo{name: name, isHistogram: false, needsMetricsNames: needsMetrics}
if len(tags) > 0 {
meter.tagOpts = make([]metrics.Opt, 0)
for i := 0; i < len(tags); i += 2 {
meter.tagOpts = append(meter.tagOpts, metrics.WithLabel(tags[i], tags[i+1]))
}
}
return meter
}
func (m *meterInfo) init(sample original_metrics.Sample) {
if !m.isHistogram {
m.gaugeMetric = metrics.NewGauge(m.name, func() float64 {
return m.gaugeValue
}, m.tagOpts...)
return
}
original_metrics.Read([]original_metrics.Sample{sample})
m.initHistogramIfNeeds(sample)
}
func (m *meterInfo) initWithCombined() {
m.combinedGaugeMetric = metrics.NewGauge(m.name, func() float64 {
return m.combinedGaugeValue
}, m.tagOpts...)
}
func (m *meterInfo) updateMetricValue(sample original_metrics.Sample) {
if !m.isHistogram {
if v, ok := m.readingFloat64(sample); ok {
m.gaugeValue = v
}
return
}
m.initHistogramIfNeeds(sample)
if m.histogramMetric == nil || m.latestHistogramValue == nil {
return
}
histogram, ok := m.readingHistogram(sample)
if !ok {
return
}
for i, val := range histogram.Counts {
if i < m.histogramStartInx {
continue
}
if i >= len(m.latestHistogramValue) {
break
}
newestValue := int64(val)
if add := newestValue - m.latestHistogramValue[i]; add > 0 {
m.histogramMetric.ObserveWithCount(histogram.Buckets[i]*float64(m.histogramMultiple), add)
m.latestHistogramValue[i] = newestValue
}
}
}
func (m *meterInfo) initHistogramIfNeeds(sample original_metrics.Sample) {
if m.histogramMetric != nil {
return
}
histogram, ok := m.readingHistogram(sample)
if !ok {
return
}
float64s := make([]float64, 0, len(histogram.Buckets))
val := make([]int64, 0, len(histogram.Buckets))
var startInx = 0
var hasAdded = false
for _, b := range histogram.Buckets {
if b > math.MaxFloat64 || b < 0 {
if !hasAdded {
startInx++
}
continue
}
hasAdded = true
float64s = append(float64s, b*float64(m.histogramMultiple))
val = append(val, 0)
}
m.histogramStartInx = startInx
m.histogramMetric = metrics.NewHistogram(m.name, float64s, m.tagOpts...)
m.latestHistogramValue = val
}
func (m *meterInfo) readingFloat64(s original_metrics.Sample) (float64, bool) {
switch s.Value.Kind() {
case original_metrics.KindUint64:
return float64(s.Value.Uint64()), true
case original_metrics.KindFloat64:
return s.Value.Float64(), true
default:
return 0, false
}
}
func (m *meterInfo) readingHistogram(s original_metrics.Sample) (*original_metrics.Float64Histogram, bool) {
if s.Value.Kind() != original_metrics.KindFloat64Histogram {
return nil, false
}
return s.Value.Float64Histogram(), true
}
func (m *meterInfo) updateCombinedMetricValue(samples []original_metrics.Sample) {
var sum float64
for _, name := range m.needsMetricsNames {
for _, sample := range samples {
if sample.Name == name {
if v, ok := m.readingFloat64(sample); ok {
sum += v
}
break
}
}
}
m.combinedGaugeValue = sum
}