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
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ require (
howett.net/plist v1.0.1
)

require github.com/prometheus/client_golang v1.22.0

require (
cloud.google.com/go v0.121.3 // indirect
cloud.google.com/go/ai v0.12.1 // indirect
Expand All @@ -51,7 +53,9 @@ require (
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/ameshkov/dnsstamps v1.0.3 // indirect
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/ccojocar/zxcvbn-go v1.0.4 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fzipp/gocyclo v0.6.0 // indirect
Expand All @@ -67,11 +71,16 @@ require (
github.com/josharian/native v1.1.0 // indirect
github.com/jstemmer/go-junit-report/v2 v2.1.0 // indirect
github.com/kisielk/errcheck v1.9.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
Expand Down
18 changes: 18 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,16 @@ github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1O
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 h1:0b2vaepXIfMsG++IsjHiI2p4bxALD1Y2nQKGMR5zDQM=
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 h1:6lhrsTEnloDPXyeZBvSYvQf8u86jbKehZPVDDlkgDl4=
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
github.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc=
github.com/ccojocar/zxcvbn-go v1.0.4/go.mod h1:3GxGX+rHmueTUMvm5ium7irpyjmm7ikxYFOSJB21Das=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down Expand Up @@ -105,10 +109,14 @@ github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX
github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M=
github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 h1:2oDp6OOhLxQ9JBoUuysVz9UZ9uI6oLUbvAZu0x8o+vE=
Expand All @@ -126,6 +134,8 @@ github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE=
github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
Expand All @@ -141,6 +151,14 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.53.0 h1:QHX46sISpG2S03dPeZBgVIZp8dGagIaiu2FiVYvpCZI=
Expand Down
13 changes: 9 additions & 4 deletions internal/home/control.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import (
"strings"
"time"

"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/AdGuardHome/internal/version"
"github.com/AdguardTeam/golibs/httphdr"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/netutil/urlutil"
"github.com/NYTimes/gziphandler"

"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/dnsforward"
"github.com/AdguardTeam/AdGuardHome/internal/version"
)

// appendDNSAddrs is a convenient helper for appending a formatted form of DNS
Expand Down Expand Up @@ -184,6 +185,10 @@ func registerControlHandlers(web *webAPI) {
globalContext.mux.HandleFunc("/apple/doh.mobileconfig", postInstall(handleMobileConfigDoH))
globalContext.mux.HandleFunc("/apple/dot.mobileconfig", postInstall(handleMobileConfigDoT))
RegisterAuthHandlers(web)

// Register metrics endpoint without control prefix, similar to /dns-query
// Use empty method to bypass auth/gzip middleware like dns-query does
httpRegister("", "/metrics", web.metricsHandler.ServeHTTP)
Comment on lines +189 to +191
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While it makes sense to have the metrics endpoint unauthenticated like the /dns-query endpoint for Prometheus scraping, it would be good to document the security considerations of exposing metrics without authentication. Consider adding a comment explaining what information is exposed and any potential security implications.

}

// httpRegister registers an HTTP handler.
Expand Down
33 changes: 27 additions & 6 deletions internal/home/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,22 @@ import (
"sync"
"time"

"github.com/AdguardTeam/AdGuardHome/internal/updater"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/netutil/httputil"
"github.com/AdguardTeam/golibs/netutil/urlutil"
"github.com/AdguardTeam/golibs/osutil"
"github.com/NYTimes/gziphandler"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/collectors"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/quic-go/quic-go/http3"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"

"github.com/AdguardTeam/AdGuardHome/internal/metrics"
"github.com/AdguardTeam/AdGuardHome/internal/updater"
)

// TODO(a.garipov): Make configurable.
Expand Down Expand Up @@ -124,6 +129,12 @@ type webAPI struct {
// httpsServer is the server that handles HTTPS traffic. If it is not nil,
// [Web.http3Server] must also not be nil.
httpsServer httpsServer

// metricsRegistry is the Prometheus registry for metrics collection.
metricsRegistry *prometheus.Registry

// metricsHandler is the HTTP handler for serving metrics.
metricsHandler http.Handler
}

// newWebAPI creates a new instance of the web UI and API server. conf must be
Expand All @@ -133,12 +144,22 @@ type webAPI struct {
func newWebAPI(ctx context.Context, conf *webConfig) (w *webAPI) {
conf.logger.InfoContext(ctx, "initializing")

// Initialize Prometheus metrics
metricsRegistry := prometheus.NewRegistry()
metricsRegistry.MustRegister(collectors.NewGoCollector())
metricsRegistry.MustRegister(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}))

// Register DNS metrics
metrics.RegisterDNSMetrics(metricsRegistry)

w = &webAPI{
conf: conf,
logger: conf.logger,
baseLogger: conf.baseLogger,
tlsManager: conf.tlsManager,
auth: conf.auth,
conf: conf,
logger: conf.logger,
baseLogger: conf.baseLogger,
tlsManager: conf.tlsManager,
auth: conf.auth,
metricsRegistry: metricsRegistry,
metricsHandler: promhttp.HandlerFor(metricsRegistry, promhttp.HandlerOpts{}),
}

clientFS := http.FileServer(http.FS(conf.clientFS))
Expand Down
46 changes: 46 additions & 0 deletions internal/metrics/dns.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package metrics

import (
"time"

"github.com/prometheus/client_golang/prometheus"
)

// DNS query result types matching internal/stats package
const (
ResultNotFiltered = "not_filtered"
ResultFiltered = "filtered"
ResultSafeBrowsing = "safe_browsing"
ResultSafeSearch = "safe_search"
ResultParental = "parental"
ResultUnknown = "unknown"
)

// DNSQueries tracks DNS queries by their processing result
var DNSQueries = prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "adguard_dns_queries_total",
Help: "Total number of DNS queries by processing result",
}, []string{"result"})

// DNSResponseTime tracks DNS query response times using native exponential histogram
var DNSResponseTime = prometheus.NewHistogramVec(prometheus.HistogramOpts{
Name: "adguard_dns_response_time_seconds",
Help: "DNS query response time in seconds",
NativeHistogramBucketFactor: 1.1,
}, []string{"result"})

// RegisterDNSMetrics registers all DNS-related metrics with the provided registry
func RegisterDNSMetrics(registry *prometheus.Registry) {
registry.MustRegister(DNSQueries)
registry.MustRegister(DNSResponseTime)
}

// IncrementDNSQueryByResult increments counters for a specific query result type
func IncrementDNSQueryByResult(result string) {
DNSQueries.WithLabelValues(result).Inc()
}

// ObserveDNSResponseTime records a DNS query response time
func ObserveDNSResponseTime(result string, duration time.Duration) {
DNSResponseTime.WithLabelValues(result).Observe(duration.Seconds())
}
53 changes: 53 additions & 0 deletions internal/metrics/dns_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package metrics

import (
"testing"

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/testutil"
)

func TestDNSMetrics(t *testing.T) {
// Create a new counter for isolated testing
testCounter := prometheus.NewCounterVec(prometheus.CounterOpts{
Name: "test_dns_queries_by_result_total",
Help: "Test counter for DNS queries by processing result",
}, []string{"result"})

// Test incrementing queries by result
testCounter.WithLabelValues(ResultFiltered).Inc()
testCounter.WithLabelValues(ResultNotFiltered).Inc()
testCounter.WithLabelValues(ResultSafeBrowsing).Inc()
testCounter.WithLabelValues(ResultNotFiltered).Inc() // Add another not filtered

// Verify result counters
filteredValue := testutil.ToFloat64(testCounter.WithLabelValues(ResultFiltered))
if filteredValue != 1 {
t.Errorf("Expected filtered queries to be 1, got %f", filteredValue)
}

notFilteredValue := testutil.ToFloat64(testCounter.WithLabelValues(ResultNotFiltered))
if notFilteredValue != 2 {
t.Errorf("Expected not filtered queries to be 2, got %f", notFilteredValue)
}

safeBrowsingValue := testutil.ToFloat64(testCounter.WithLabelValues(ResultSafeBrowsing))
if safeBrowsingValue != 1 {
t.Errorf("Expected safe browsing queries to be 1, got %f", safeBrowsingValue)
}
}

func TestRegisterDNSMetrics(t *testing.T) {
registry := prometheus.NewRegistry()

// This should not panic
RegisterDNSMetrics(registry)

// Registering again should panic due to duplicate registration
defer func() {
if r := recover(); r == nil {
t.Error("Expected panic when registering metrics twice")
}
}()
RegisterDNSMetrics(registry)
}
24 changes: 23 additions & 1 deletion internal/stats/unit.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import (
"slices"
"time"

"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"go.etcd.io/bbolt"

"github.com/AdguardTeam/AdGuardHome/internal/aghnet"
"github.com/AdguardTeam/AdGuardHome/internal/metrics"
)

const (
Expand Down Expand Up @@ -328,6 +330,26 @@ func (u *unit) add(e *Entry) {
u.timeSum += pt
u.nTotal++

// Update Prometheus metrics
// Map Result constants to metrics labels
var resultLabel string
switch e.Result {
case RNotFiltered:
resultLabel = metrics.ResultNotFiltered
case RFiltered:
resultLabel = metrics.ResultFiltered
case RSafeBrowsing:
resultLabel = metrics.ResultSafeBrowsing
case RSafeSearch:
resultLabel = metrics.ResultSafeSearch
case RParental:
resultLabel = metrics.ResultParental
default:
resultLabel = metrics.ResultUnknown
}
metrics.IncrementDNSQueryByResult(resultLabel)
metrics.ObserveDNSResponseTime(resultLabel, e.ProcessingTime)

for _, s := range e.UpstreamStats {
if s.IsCached || s.Error != nil {
continue
Expand Down