mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	Merge pull request #59719 from hzxuzhonghu/pprof-profiling
Automatic merge from submit-queue (batch tested with PRs 59463, 59719, 60181, 58283, 59966). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. components pprof profiling make use of existing genericapiserver's **What this PR does / why we need it**: fix #60278 Instead of writing private pprof, all components make use of generic apiserver existing profiling. **Release note**: ```release-note NONE ```
This commit is contained in:
		@@ -1,14 +1,10 @@
 | 
				
			|||||||
package(default_visibility = ["//visibility:public"])
 | 
					load("@io_bazel_rules_go//go:def.bzl", "go_library")
 | 
				
			||||||
 | 
					 | 
				
			||||||
load(
 | 
					 | 
				
			||||||
    "@io_bazel_rules_go//go:def.bzl",
 | 
					 | 
				
			||||||
    "go_library",
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
go_library(
 | 
					go_library(
 | 
				
			||||||
    name = "go_default_library",
 | 
					    name = "go_default_library",
 | 
				
			||||||
    srcs = ["controllermanager.go"],
 | 
					    srcs = ["controllermanager.go"],
 | 
				
			||||||
    importpath = "k8s.io/kubernetes/cmd/cloud-controller-manager/app",
 | 
					    importpath = "k8s.io/kubernetes/cmd/cloud-controller-manager/app",
 | 
				
			||||||
 | 
					    visibility = ["//visibility:public"],
 | 
				
			||||||
    deps = [
 | 
					    deps = [
 | 
				
			||||||
        "//cmd/cloud-controller-manager/app/config:go_default_library",
 | 
					        "//cmd/cloud-controller-manager/app/config:go_default_library",
 | 
				
			||||||
        "//cmd/cloud-controller-manager/app/options:go_default_library",
 | 
					        "//cmd/cloud-controller-manager/app/options:go_default_library",
 | 
				
			||||||
@@ -49,4 +45,5 @@ filegroup(
 | 
				
			|||||||
        "//cmd/cloud-controller-manager/app/options:all-srcs",
 | 
					        "//cmd/cloud-controller-manager/app/options:all-srcs",
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    tags = ["automanaged"],
 | 
					    tags = ["automanaged"],
 | 
				
			||||||
 | 
					    visibility = ["//visibility:public"],
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,6 +20,8 @@ go_library(
 | 
				
			|||||||
        "//vendor/k8s.io/apiserver/pkg/server:go_default_library",
 | 
					        "//vendor/k8s.io/apiserver/pkg/server:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/apiserver/pkg/server/filters:go_default_library",
 | 
					        "//vendor/k8s.io/apiserver/pkg/server/filters:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/apiserver/pkg/server/healthz:go_default_library",
 | 
					        "//vendor/k8s.io/apiserver/pkg/server/healthz:go_default_library",
 | 
				
			||||||
 | 
					        "//vendor/k8s.io/apiserver/pkg/server/mux:go_default_library",
 | 
				
			||||||
 | 
					        "//vendor/k8s.io/apiserver/pkg/server/routes:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/client-go/kubernetes:go_default_library",
 | 
					        "//vendor/k8s.io/client-go/kubernetes:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/client-go/rest:go_default_library",
 | 
					        "//vendor/k8s.io/client-go/rest:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/client-go/tools/record:go_default_library",
 | 
					        "//vendor/k8s.io/client-go/tools/record:go_default_library",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,7 +18,6 @@ package app
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"net/http/pprof"
 | 
					 | 
				
			||||||
	goruntime "runtime"
 | 
						goruntime "runtime"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -28,6 +27,8 @@ import (
 | 
				
			|||||||
	apirequest "k8s.io/apiserver/pkg/endpoints/request"
 | 
						apirequest "k8s.io/apiserver/pkg/endpoints/request"
 | 
				
			||||||
	genericfilters "k8s.io/apiserver/pkg/server/filters"
 | 
						genericfilters "k8s.io/apiserver/pkg/server/filters"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/server/healthz"
 | 
						"k8s.io/apiserver/pkg/server/healthz"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/server/mux"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/server/routes"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/api/legacyscheme"
 | 
						"k8s.io/kubernetes/pkg/api/legacyscheme"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/util/configz"
 | 
						"k8s.io/kubernetes/pkg/util/configz"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -37,13 +38,10 @@ type serveFunc func(handler http.Handler, shutdownTimeout time.Duration, stopCh
 | 
				
			|||||||
// Serve creates a base handler chain for a controller manager. It runs the
 | 
					// Serve creates a base handler chain for a controller manager. It runs the
 | 
				
			||||||
// the chain with the given serveFunc.
 | 
					// the chain with the given serveFunc.
 | 
				
			||||||
func Serve(c *CompletedConfig, serveFunc serveFunc, stopCh <-chan struct{}) error {
 | 
					func Serve(c *CompletedConfig, serveFunc serveFunc, stopCh <-chan struct{}) error {
 | 
				
			||||||
	mux := http.NewServeMux()
 | 
						mux := mux.NewPathRecorderMux("controller-manager")
 | 
				
			||||||
	healthz.InstallHandler(mux)
 | 
						healthz.InstallHandler(mux)
 | 
				
			||||||
	if c.ComponentConfig.EnableProfiling {
 | 
						if c.ComponentConfig.EnableProfiling {
 | 
				
			||||||
		mux.HandleFunc("/debug/pprof/", pprof.Index)
 | 
							routes.Profiling{}.Install(mux)
 | 
				
			||||||
		mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
 | 
					 | 
				
			||||||
		mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
 | 
					 | 
				
			||||||
		mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
 | 
					 | 
				
			||||||
		if c.ComponentConfig.EnableContentionProfiling {
 | 
							if c.ComponentConfig.EnableContentionProfiling {
 | 
				
			||||||
			goruntime.SetBlockProfileRate(1)
 | 
								goruntime.SetBlockProfileRate(1)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,4 @@
 | 
				
			|||||||
package(default_visibility = ["//visibility:public"])
 | 
					load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
 | 
				
			||||||
 | 
					 | 
				
			||||||
load(
 | 
					 | 
				
			||||||
    "@io_bazel_rules_go//go:def.bzl",
 | 
					 | 
				
			||||||
    "go_library",
 | 
					 | 
				
			||||||
    "go_test",
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
go_library(
 | 
					go_library(
 | 
				
			||||||
    name = "go_default_library",
 | 
					    name = "go_default_library",
 | 
				
			||||||
@@ -24,6 +18,7 @@ go_library(
 | 
				
			|||||||
        "rbac.go",
 | 
					        "rbac.go",
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    importpath = "k8s.io/kubernetes/cmd/kube-controller-manager/app",
 | 
					    importpath = "k8s.io/kubernetes/cmd/kube-controller-manager/app",
 | 
				
			||||||
 | 
					    visibility = ["//visibility:public"],
 | 
				
			||||||
    deps = [
 | 
					    deps = [
 | 
				
			||||||
        "//cmd/controller-manager/app:go_default_library",
 | 
					        "//cmd/controller-manager/app:go_default_library",
 | 
				
			||||||
        "//cmd/controller-manager/app/options:go_default_library",
 | 
					        "//cmd/controller-manager/app/options:go_default_library",
 | 
				
			||||||
@@ -137,6 +132,16 @@ go_library(
 | 
				
			|||||||
    ],
 | 
					    ],
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					go_test(
 | 
				
			||||||
 | 
					    name = "go_default_test",
 | 
				
			||||||
 | 
					    srcs = ["controller_manager_test.go"],
 | 
				
			||||||
 | 
					    embed = [":go_default_library"],
 | 
				
			||||||
 | 
					    deps = [
 | 
				
			||||||
 | 
					        "//vendor/github.com/stretchr/testify/assert:go_default_library",
 | 
				
			||||||
 | 
					        "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
filegroup(
 | 
					filegroup(
 | 
				
			||||||
    name = "package-srcs",
 | 
					    name = "package-srcs",
 | 
				
			||||||
    srcs = glob(["**"]),
 | 
					    srcs = glob(["**"]),
 | 
				
			||||||
@@ -152,14 +157,5 @@ filegroup(
 | 
				
			|||||||
        "//cmd/kube-controller-manager/app/options:all-srcs",
 | 
					        "//cmd/kube-controller-manager/app/options:all-srcs",
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    tags = ["automanaged"],
 | 
					    tags = ["automanaged"],
 | 
				
			||||||
)
 | 
					    visibility = ["//visibility:public"],
 | 
				
			||||||
 | 
					 | 
				
			||||||
go_test(
 | 
					 | 
				
			||||||
    name = "go_default_test",
 | 
					 | 
				
			||||||
    srcs = ["controller_manager_test.go"],
 | 
					 | 
				
			||||||
    embed = [":go_default_library"],
 | 
					 | 
				
			||||||
    deps = [
 | 
					 | 
				
			||||||
        "//vendor/github.com/stretchr/testify/assert:go_default_library",
 | 
					 | 
				
			||||||
        "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -91,6 +91,8 @@ go_library(
 | 
				
			|||||||
        "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
 | 
					        "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
 | 
					        "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/apiserver/pkg/server/healthz:go_default_library",
 | 
					        "//vendor/k8s.io/apiserver/pkg/server/healthz:go_default_library",
 | 
				
			||||||
 | 
					        "//vendor/k8s.io/apiserver/pkg/server/mux:go_default_library",
 | 
				
			||||||
 | 
					        "//vendor/k8s.io/apiserver/pkg/server/routes:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
 | 
					        "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
 | 
					        "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/client-go/kubernetes:go_default_library",
 | 
					        "//vendor/k8s.io/client-go/kubernetes:go_default_library",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,7 +24,6 @@ import (
 | 
				
			|||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
	"net"
 | 
						"net"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"net/http/pprof"
 | 
					 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	goruntime "runtime"
 | 
						goruntime "runtime"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
@@ -38,6 +37,8 @@ import (
 | 
				
			|||||||
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 | 
						utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/wait"
 | 
						"k8s.io/apimachinery/pkg/util/wait"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/server/healthz"
 | 
						"k8s.io/apiserver/pkg/server/healthz"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/server/mux"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/server/routes"
 | 
				
			||||||
	utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
						utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/util/flag"
 | 
						"k8s.io/apiserver/pkg/util/flag"
 | 
				
			||||||
	clientgoclientset "k8s.io/client-go/kubernetes"
 | 
						clientgoclientset "k8s.io/client-go/kubernetes"
 | 
				
			||||||
@@ -472,17 +473,14 @@ func (s *ProxyServer) Run() error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// Start up a metrics server if requested
 | 
						// Start up a metrics server if requested
 | 
				
			||||||
	if len(s.MetricsBindAddress) > 0 {
 | 
						if len(s.MetricsBindAddress) > 0 {
 | 
				
			||||||
		mux := http.NewServeMux()
 | 
							mux := mux.NewPathRecorderMux("kube-proxy")
 | 
				
			||||||
		healthz.InstallHandler(mux)
 | 
							healthz.InstallHandler(mux)
 | 
				
			||||||
		mux.HandleFunc("/proxyMode", func(w http.ResponseWriter, r *http.Request) {
 | 
							mux.HandleFunc("/proxyMode", func(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
			fmt.Fprintf(w, "%s", s.ProxyMode)
 | 
								fmt.Fprintf(w, "%s", s.ProxyMode)
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
		mux.Handle("/metrics", prometheus.Handler())
 | 
							mux.Handle("/metrics", prometheus.Handler())
 | 
				
			||||||
		if s.EnableProfiling {
 | 
							if s.EnableProfiling {
 | 
				
			||||||
			mux.HandleFunc("/debug/pprof/", pprof.Index)
 | 
								routes.Profiling{}.Install(mux)
 | 
				
			||||||
			mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
 | 
					 | 
				
			||||||
			mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
 | 
					 | 
				
			||||||
			mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		configz.InstallHandler(mux)
 | 
							configz.InstallHandler(mux)
 | 
				
			||||||
		go wait.Until(func() {
 | 
							go wait.Until(func() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,6 +39,8 @@ go_library(
 | 
				
			|||||||
        "//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
 | 
					        "//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
 | 
					        "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/apiserver/pkg/server/healthz:go_default_library",
 | 
					        "//vendor/k8s.io/apiserver/pkg/server/healthz:go_default_library",
 | 
				
			||||||
 | 
					        "//vendor/k8s.io/apiserver/pkg/server/mux:go_default_library",
 | 
				
			||||||
 | 
					        "//vendor/k8s.io/apiserver/pkg/server/routes:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
 | 
					        "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/client-go/informers:go_default_library",
 | 
					        "//vendor/k8s.io/client-go/informers:go_default_library",
 | 
				
			||||||
        "//vendor/k8s.io/client-go/informers/core/v1:go_default_library",
 | 
					        "//vendor/k8s.io/client-go/informers/core/v1:go_default_library",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,7 +23,6 @@ import (
 | 
				
			|||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
	"net"
 | 
						"net"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"net/http/pprof"
 | 
					 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"reflect"
 | 
						"reflect"
 | 
				
			||||||
	goruntime "runtime"
 | 
						goruntime "runtime"
 | 
				
			||||||
@@ -38,6 +37,8 @@ import (
 | 
				
			|||||||
	"k8s.io/apimachinery/pkg/util/uuid"
 | 
						"k8s.io/apimachinery/pkg/util/uuid"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/wait"
 | 
						"k8s.io/apimachinery/pkg/util/wait"
 | 
				
			||||||
	"k8s.io/apiserver/pkg/server/healthz"
 | 
						"k8s.io/apiserver/pkg/server/healthz"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/server/mux"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/server/routes"
 | 
				
			||||||
	utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
						utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
				
			||||||
	"k8s.io/client-go/informers"
 | 
						"k8s.io/client-go/informers"
 | 
				
			||||||
	coreinformers "k8s.io/client-go/informers/core/v1"
 | 
						coreinformers "k8s.io/client-go/informers/core/v1"
 | 
				
			||||||
@@ -470,17 +471,14 @@ func makeLeaderElectionConfig(config componentconfig.KubeSchedulerLeaderElection
 | 
				
			|||||||
// embed the metrics handler if the healthz and metrics address configurations
 | 
					// embed the metrics handler if the healthz and metrics address configurations
 | 
				
			||||||
// are the same.
 | 
					// are the same.
 | 
				
			||||||
func makeHealthzServer(config *componentconfig.KubeSchedulerConfiguration) *http.Server {
 | 
					func makeHealthzServer(config *componentconfig.KubeSchedulerConfiguration) *http.Server {
 | 
				
			||||||
	mux := http.NewServeMux()
 | 
						mux := mux.NewPathRecorderMux("kube-scheduler")
 | 
				
			||||||
	healthz.InstallHandler(mux)
 | 
						healthz.InstallHandler(mux)
 | 
				
			||||||
	if config.HealthzBindAddress == config.MetricsBindAddress {
 | 
						if config.HealthzBindAddress == config.MetricsBindAddress {
 | 
				
			||||||
		configz.InstallHandler(mux)
 | 
							configz.InstallHandler(mux)
 | 
				
			||||||
		mux.Handle("/metrics", prometheus.Handler())
 | 
							mux.Handle("/metrics", prometheus.Handler())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if config.EnableProfiling {
 | 
						if config.EnableProfiling {
 | 
				
			||||||
		mux.HandleFunc("/debug/pprof/", pprof.Index)
 | 
							routes.Profiling{}.Install(mux)
 | 
				
			||||||
		mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
 | 
					 | 
				
			||||||
		mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
 | 
					 | 
				
			||||||
		mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
 | 
					 | 
				
			||||||
		if config.EnableContentionProfiling {
 | 
							if config.EnableContentionProfiling {
 | 
				
			||||||
			goruntime.SetBlockProfileRate(1)
 | 
								goruntime.SetBlockProfileRate(1)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -493,14 +491,11 @@ func makeHealthzServer(config *componentconfig.KubeSchedulerConfiguration) *http
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// makeMetricsServer builds a metrics server from the config.
 | 
					// makeMetricsServer builds a metrics server from the config.
 | 
				
			||||||
func makeMetricsServer(config *componentconfig.KubeSchedulerConfiguration) *http.Server {
 | 
					func makeMetricsServer(config *componentconfig.KubeSchedulerConfiguration) *http.Server {
 | 
				
			||||||
	mux := http.NewServeMux()
 | 
						mux := mux.NewPathRecorderMux("kube-scheduler")
 | 
				
			||||||
	configz.InstallHandler(mux)
 | 
						configz.InstallHandler(mux)
 | 
				
			||||||
	mux.Handle("/metrics", prometheus.Handler())
 | 
						mux.Handle("/metrics", prometheus.Handler())
 | 
				
			||||||
	if config.EnableProfiling {
 | 
						if config.EnableProfiling {
 | 
				
			||||||
		mux.HandleFunc("/debug/pprof/", pprof.Index)
 | 
							routes.Profiling{}.Install(mux)
 | 
				
			||||||
		mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
 | 
					 | 
				
			||||||
		mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
 | 
					 | 
				
			||||||
		mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
 | 
					 | 
				
			||||||
		if config.EnableContentionProfiling {
 | 
							if config.EnableContentionProfiling {
 | 
				
			||||||
			goruntime.SetBlockProfileRate(1)
 | 
								goruntime.SetBlockProfileRate(1)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user