mirror of
https://github.com/fatedier/frp.git
synced 2026-11-05 07:41:36 +08:00
Move client_golang to v1.25.0 with its required module updates (client_model v0.6.3, common v0.72.0, procfs v0.22.0, xxhash v2.3.0, protobuf v1.36.12, munnerz/goautoneg). go.mod keeps go 1.26.0 and has no toolchain line. Sync the frpc text metrics clone with the new default Go collector: add go_gc_gogc_percent, go_gc_gomemlimit_bytes and go_sched_gomaxprocs_threads, update the go_memstats help texts, and remove go_memstats_lookups_total. frpc still exposes go_* metrics only.
126 lines
4.1 KiB
Go
126 lines
4.1 KiB
Go
// Copyright 2026 The frp Authors
|
|
//
|
|
// 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 text
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/prometheus/client_golang/prometheus/collectors"
|
|
dto "github.com/prometheus/client_model/go"
|
|
"github.com/prometheus/common/expfmt"
|
|
"github.com/prometheus/common/model"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// gather returns what frpc would serve.
|
|
func gather(t *testing.T) string {
|
|
t.Helper()
|
|
reg := NewRegistry()
|
|
reg.RegisterRuntime()
|
|
var b strings.Builder
|
|
require.NoError(t, reg.WritePrometheus(&b))
|
|
return b.String()
|
|
}
|
|
|
|
// parseFamilies decodes the text exposition format with the Prometheus parser,
|
|
// so anything a scraper would reject fails the test.
|
|
func parseFamilies(t *testing.T, body string) map[string]*dto.MetricFamily {
|
|
t.Helper()
|
|
parser := expfmt.NewTextParser(model.UTF8Validation)
|
|
families, err := parser.TextToMetricFamilies(strings.NewReader(body))
|
|
require.NoError(t, err, "output must be valid text exposition format")
|
|
return families
|
|
}
|
|
|
|
// TestMatchesClientGolang checks that every go_* family this package emits has
|
|
// the same name, type and help text as client_golang's default Go collector. The
|
|
// official collector is used as the oracle, so a difference shows up here rather
|
|
// than in someone's dashboard.
|
|
func TestMatchesClientGolang(t *testing.T) {
|
|
reg := prometheus.NewRegistry()
|
|
reg.MustRegister(collectors.NewGoCollector())
|
|
mfs, err := reg.Gather()
|
|
require.NoError(t, err)
|
|
|
|
reference := make(map[string]*dto.MetricFamily, len(mfs))
|
|
for _, mf := range mfs {
|
|
reference[mf.GetName()] = mf
|
|
}
|
|
ours := parseFamilies(t, gather(t))
|
|
|
|
checked := 0
|
|
for name, want := range reference {
|
|
if !strings.HasPrefix(name, "go_") {
|
|
continue
|
|
}
|
|
got, ok := ours[name]
|
|
require.True(t, ok, "%s is missing", name)
|
|
require.Equal(t, want.GetType(), got.GetType(), "%s type", name)
|
|
require.Equal(t, want.GetHelp(), got.GetHelp(), "%s help", name)
|
|
checked++
|
|
}
|
|
require.Greater(t, checked, 20, "comparison covered too few families")
|
|
}
|
|
|
|
// TestNoBusinessMetrics pins what frpc must not expose: no frp_ business metrics,
|
|
// none of the handler instrumentation client_golang adds, and no process_
|
|
// families. frpc reports Go runtime metrics only, on every platform.
|
|
func TestNoBusinessMetrics(t *testing.T) {
|
|
body := gather(t)
|
|
require.NotContains(t, body, "frp_")
|
|
require.NotContains(t, body, "promhttp_")
|
|
require.NotContains(t, body, "process_")
|
|
}
|
|
|
|
func TestHandler(t *testing.T) {
|
|
reg := NewRegistry()
|
|
reg.RegisterRuntime()
|
|
rec := httptest.NewRecorder()
|
|
reg.Handler().ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/metrics", nil))
|
|
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
require.Equal(t, ContentType, rec.Header().Get("Content-Type"))
|
|
|
|
families := parseFamilies(t, rec.Body.String())
|
|
require.Contains(t, families, "go_goroutines")
|
|
gc, ok := families["go_gc_duration_seconds"]
|
|
require.True(t, ok)
|
|
require.Equal(t, dto.MetricType_SUMMARY, gc.GetType())
|
|
require.Len(t, gc.GetMetric()[0].GetSummary().GetQuantile(), 5)
|
|
}
|
|
|
|
func TestEmptyRegistry(t *testing.T) {
|
|
var b strings.Builder
|
|
require.NoError(t, NewRegistry().WritePrometheus(&b))
|
|
require.Empty(t, b.String())
|
|
}
|
|
|
|
func TestEscaping(t *testing.T) {
|
|
var b strings.Builder
|
|
require.NoError(t, encode(&b, []family{{
|
|
name: "test_labels",
|
|
help: "help with \\ and\nnewline",
|
|
typ: typeGauge,
|
|
samples: []sample{{labels: []label{{name: "v", value: "a\"b\\c\nd"}}, value: 1}},
|
|
}}))
|
|
require.Equal(t, "# HELP test_labels help with \\\\ and\\nnewline\n"+
|
|
"# TYPE test_labels gauge\n"+
|
|
"test_labels{v=\"a\\\"b\\\\c\\nd\"} 1\n", b.String())
|
|
}
|