@ -21,6 +21,7 @@ import (
"runtime"
"runtime"
"strconv"
"strconv"
"strings"
"strings"
"sync"
"time"
"time"
"inet.af/netaddr"
"inet.af/netaddr"
@ -41,6 +42,16 @@ func randHex(n int) string {
return hex . EncodeToString ( b )
return hex . EncodeToString ( b )
}
}
var (
// The clientmetrics package is stateful, but we want to expose a simple
// imperative API to local clients, so we need to keep track of
// clientmetric.Metric instances that we've created for them. These need to
// be globals because we end up creating many Handler instances for the
// lifetime of a client.
metricsMu sync . Mutex
metrics = map [ string ] * clientmetric . Metric { }
)
func NewHandler ( b * ipnlocal . LocalBackend , logf logger . Logf , logID string ) * Handler {
func NewHandler ( b * ipnlocal . LocalBackend , logf logger . Logf , logID string ) * Handler {
return & Handler { b : b , logf : logf , backendLogID : logID }
return & Handler { b : b , logf : logf , backendLogID : logID }
}
}
@ -137,6 +148,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h . serveDial ( w , r )
h . serveDial ( w , r )
case "/localapi/v0/id-token" :
case "/localapi/v0/id-token" :
h . serveIDToken ( w , r )
h . serveIDToken ( w , r )
case "/localapi/v0/upload-client-metrics" :
h . serveUploadClientMetrics ( w , r )
case "/" :
case "/" :
io . WriteString ( w , "tailscaled\n" )
io . WriteString ( w , "tailscaled\n" )
default :
default :
@ -730,6 +743,54 @@ func (h *Handler) serveDial(w http.ResponseWriter, r *http.Request) {
<- errc
<- errc
}
}
func ( h * Handler ) serveUploadClientMetrics ( w http . ResponseWriter , r * http . Request ) {
if r . Method != "POST" {
http . Error ( w , "unsupported method" , http . StatusMethodNotAllowed )
return
}
type clientMetricJSON struct {
Name string ` json:"name" `
// One of "counter" or "gauge"
Type string ` json:"type" `
Value int ` json:"value" `
}
var clientMetrics [ ] clientMetricJSON
if err := json . NewDecoder ( r . Body ) . Decode ( & clientMetrics ) ; err != nil {
http . Error ( w , "invalid JSON body" , 400 )
return
}
metricsMu . Lock ( )
defer metricsMu . Unlock ( )
for _ , m := range clientMetrics {
if metric , ok := metrics [ m . Name ] ; ok {
metric . Add ( int64 ( m . Value ) )
} else {
if clientmetric . HasPublished ( m . Name ) {
http . Error ( w , "Already have a metric named " + m . Name , 400 )
return
}
var metric * clientmetric . Metric
switch m . Type {
case "counter" :
metric = clientmetric . NewCounter ( m . Name )
case "gauge" :
metric = clientmetric . NewGauge ( m . Name )
default :
http . Error ( w , "Unknown metric type " + m . Type , 400 )
return
}
metrics [ m . Name ] = metric
metric . Add ( int64 ( m . Value ) )
}
}
w . Header ( ) . Set ( "Content-Type" , "application/json" )
json . NewEncoder ( w ) . Encode ( struct { } { } )
}
func defBool ( a string , def bool ) bool {
func defBool ( a string , def bool ) bool {
if a == "" {
if a == "" {
return def
return def