FFFF Metrics exporter: read the key-value asynchronous metrics of ClickHouse 26.8 by lizardoluis · Pull Request #2136 · Altinity/clickhouse-operator · GitHub
[go: up one dir, main page]

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions pkg/metrics/clickhouse/async_metrics_legacy_names.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright 2019 Altinity Ltd and/or its affiliates. All rights reserved.
//
// Licensed 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 clickhouse

import "strings"

// legacyAsyncMetricName is the name a key-value asynchronous metric element had before
// ClickHouse 26.8, or "" for a family that has none. It mirrors getLegacyAsynchronousMetricName
// in src/Common/AsynchronousMetrics.cpp. The table is historical: families added later never
// had a legacy name.
func legacyAsyncMetricName(metric, key string) string {
if metric == "Temperature" {
// Thermal zones have numeric keys appended as is, hardware monitors after an underscore.
if isNumeric(key) {
return metric + key
}
return metric + "_" + key
}
affix, ok := legacyAsyncMetricAffixes[metric]
if !ok {
return ""
}
return affix.prefix + key + affix.suffix
}

type legacyAffix struct {
prefix string
suffix string
}

var legacyAsyncMetricAffixes = buildLegacyAsyncMetricAffixes()

func buildLegacyAsyncMetricAffixes() map[string]legacyAffix {
res := map[string]legacyAffix{}

// The key was appended to the name: OSUserTimeCPU3
for _, metric := range strings.Fields(`
OSUserTimeCPU OSNiceTimeCPU OSSystemTimeCPU OSIdleTimeCPU OSIOWaitTimeCPU
OSIrqTimeCPU OSSoftIrqTimeCPU OSStealTimeCPU OSGuestTimeCPU OSGuestNiceTimeCPU`) {
res[metric] = legacyAffix{prefix: metric}
}

// The key was appended after an underscore: BlockReadBytes_sda
for _, metric := range strings.Fields(`
CPUFrequencyMHz
BlockReadOps BlockWriteOps BlockDiscardOps
BlockReadMerges BlockWriteMerges BlockDiscardMerges
BlockReadBytes BlockWriteBytes BlockDiscardBytes
BlockReadTime BlockWriteTime BlockDiscardTime
BlockInFlightOps BlockActiveTime BlockQueueTime
BlockActiveTimePerOp BlockQueueTimePerOp
NetworkReceiveBytes NetworkReceivePackets NetworkReceiveErrors NetworkReceiveDrop
NetworkSendBytes NetworkSendPackets NetworkSendErrors NetworkSendDrop
DiskTotal DiskUsed DiskAvailable DiskUnreserved
DiskPutObjectThrottlerRPS DiskPutObjectThrottlerAvailable
DiskGetObjectThrottlerRPS DiskGetObjectThrottlerAvailable`) {
res[metric] = legacyAffix{prefix: metric + "_"}
}

// The key was in the middle of the name: EDAC0_Correctable, AsyncLoggingTextLogQueueSize
res["EDACCorrectable"] = legacyAffix{prefix: "EDAC", suffix: "_Correctable"}
res["EDACUncorrectable"] = legacyAffix{prefix: "EDAC", suffix: "_Uncorrectable"}
res["AsyncLoggingQueueSize"] = legacyAffix{prefix: "AsyncLogging", suffix: "QueueSize"}

// The key was a prefix of the name: s3_diskDeadBlobsQueueEstimate
res["DeadBlobsQueueEstimate"] = legacyAffix{suffix: "DeadBlobsQueueEstimate"}
res["MissingBlobsQueueEstimate"] = legacyAffix{suffix: "MissingBlobsQueueEstimate"}

return res
}

func isNumeric(s string) bool {
if s == "" {
return false
}
for _, c := range s {
if c < '0' || c > '9' {
return false
}
}
return true
}
53 changes: 53 additions & 0 deletions pkg/metrics/clickhouse/async_metrics_legacy_names_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2019 Altinity Ltd and/or its affiliates. All rights reserved.
//
// Licensed 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 clickhouse

import "testing"

// The cases mirror src/Common/tests/gtest_async_metrics_key_values_mode.cpp of ClickHouse.
func TestLegacyAsyncMetricName(t *testing.T) {
for _, c := range []struct{ metric, key, want string }{
{"OSUserTimeCPU", "3", "OSUserTimeCPU3"},
{"OSGuestNiceTimeCPU", "11", "OSGuestNiceTimeCPU11"},
{"BlockReadBytes", "sda", "BlockReadBytes_sda"},
{"BlockActiveTimePerOp", "nvme0n1", "BlockActiveTimePerOp_nvme0n1"},
{"NetworkReceiveDrop", "eth0", "NetworkReceiveDrop_eth0"},
{"CPUFrequencyMHz", "7", "CPUFrequencyMHz_7"},
{"DiskTotal", "default", "DiskTotal_default"},
{"DiskGetObjectThrottlerRPS", "s3", "DiskGetObjectThrottlerRPS_s3"},
{"EDACCorrectable", "0", "EDAC0_Correctable"},
{"EDACUncorrectable", "1", "EDAC1_Uncorrectable"},
{"AsyncLoggingQueueSize", "TextLog", "AsyncLoggingTextLogQueueSize"},
{"DeadBlobsQueueEstimate", "s3_disk", "s3_diskDeadBlobsQueueEstimate"},
{"MissingBlobsQueueEstimate", "s3_disk", "s3_diskMissingBlobsQueueEstimate"},
{"Temperature", "3", "Temperature3"},
{"Temperature", "coretemp_Core_0", "Temperature_coretemp_Core_0"},
{"Temperature", "0x_sensor", "Temperature_0x_sensor"},
{"SomeFamilyAddedLater", "key", ""},
{"Uptime", "", ""},
} {
if got := legacyAsyncMetricName(c.metric, c.key); got != c.want {
t.Errorf("legacyAsyncMetricName(%q, %q) = %q, want %q", c.metric, c.key, got, c.want)
}
}
}

// TestLegacyAsyncMetricNamesCount guards against dropping a family while editing the table.
func TestLegacyAsyncMetricNamesCount(t *testing.T) {
// 49 families in the table plus Temperature, handled separately
if got := len(legacyAsyncMetricAffixes); got != 49 {
t.Fatalf("expected 49 families, got %d", got)
}
}
71 changes: 71 additions & 0 deletions pkg/metrics/clickhouse/async_metrics_probe_cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2019 Altinity Ltd and/or its affiliates. All rights reserved.
//
// Licensed 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 clickhouse

import (
"sync"
"time"
)

// A host without the key_values column is probed again after this long, so an in place
// upgrade to ClickHouse 26.8 is noticed without restarting the exporter.
const asyncKeyValuesNegativeTTL = 5 * time.Minute

type asyncKeyValuesEntry struct {
supported bool
probedAt time.Time
}

// asyncKeyValuesCache remembers per host whether system.asynchronous_metrics has the key_values column.
// A positive answer is kept until a metrics query fails (a downgrade), a negative one expires.
type asyncKeyValuesCache struct {
mu sync.Mutex
hosts map[string]asyncKeyValuesEntry
}

func newAsyncKeyValuesCache() *asyncKeyValuesCache {
return &asyncKeyValuesCache{hosts: map[string]asyncKeyValuesEntry{}}
}

func (c *asyncKeyValuesCache) get(host string) (supported, known bool) {
if c == nil {
return false, false
}
c.mu.Lock()
defer c.mu.Unlock()
entry, ok := c.hosts[host]
if !ok || (!entry.supported && time.Since(entry.probedAt) > asyncKeyValuesNegativeTTL) {
return false, false
}
return entry.supported, true
}

func (c *asyncKeyValuesCache) set(host string, supported bool) {
if c == nil {
return
}
c.mu.Lock()
defer c.mu.Unlock()
c.hosts[host] = asyncKeyValuesEntry{supported: supported, probedAt: time.Now()}
}

func (c *asyncKeyValuesCache) forget(host string) {
if c == nil {
return
}
c.mu.Lock()
defer c.mu.Unlock()
delete(c.hosts, host)
}
Loading
Loading
0