生命不止,继续 go go go!!!
基础还是要打好,很久没有分享golang的标准包了,今天就来一个expvar包。
Package expvar
概述
Package expvar provides a standardized interface to public variables,such as operation counters in servers. It exposes these variables via HTTP at /debug/vars in JSON format.
Operations to set or modify these public variables are atomic.
In addition to adding the HTTP handler,this package registers the following variables:
cmdline os.Args
memstats runtime.Memstats
The package is sometimes only imported for the side effect of registering its HTTP handler and the above variables. To use it this way,link this package into your program:
import _ "expvar"
expvar包提供了公共变量的标准接口,如服务的操作计数器。本包通过HTTP在/debug/vars位置以JSON格式导出了这些变量。+
对这些公共变量的读写操作都是原子级的。
为了增加HTTP处理器,本包注册了如下变量:+
cmdline os.Args
memstats runtime.Memstats
有时候本包被导入只是为了获得本包注册HTTP处理器和上述变量的副作用。此时可以如下方式导入本包:
import _ "expvar"
支持类型
支持一些常见的类型:float64、int64、Map、String。
如果我们的程序要用到上面提的四种类型(其中,Map 类型要求 Key 是字符串)。可以考虑使用这个包。
整形类型,可以用来做计数器
线程安全的
此外还提供了调试接口,/debug/vars。它能够展示所有通过这个包创建的变量
所有的变量都是Var类型,可以自己通过实现这个接口扩展其它的类型
Handler()方法可以得到调试接口的http.Handler,和自己的路由对接
func Do
func Do(f func(KeyValue))
Do calls f for each exported variable. The global variable map is locked during the iteration,but existing entries may be concurrently updated.
Do对映射的每一条记录都调用f。迭代执行时会锁定该映射,但已存在的记录可以同时更新。
func Handler
func Handler() http.Handler
Handler returns the expvar HTTP Handler.
This is only needed to install the handler in a non-standard location.
func Publish
func Publish(name string,v Var)
Publish declares a named exported variable. This should be called from a package’s init function when it creates its Vars. If the name is already registered then this will log.Panic.
Publish声明一个导出变量。必须在init函数里调用。如果name已经被注册,会调用log.Panic。
源码
由于expvar的源码篇幅不是很大,就贴上了:
package expvar
import (
"bytes"
"encoding/json"
"fmt"
"log"
"math"
"net/http"
"os"
"runtime"
"sort"
"strconv"
"sync"
"sync/atomic"
)
// Var is an abstract type for all exported variables.
type Var interface {
// String returns a valid JSON value for the variable.
// Types with String methods that do not return valid JSON
// (such as time.Time) must not be used as a Var.
String() string
}
// Int is a 64-bit integer variable that satisfies the Var interface.
type Int struct {
i int64
}
func (v *Int) Value() int64 {
return atomic.LoadInt64(&v.i)
}
func (v *Int) String() string {
return strconv.FormatInt(atomic.LoadInt64(&v.i), 10)
}
func (v *Int) Add(delta int64) {
atomic.AddInt64(&v.i,delta)
}
func (v *Int) Set(value int64) {
atomic.StoreInt64(&v.i,value)
}
// Float is a 64-bit float variable that satisfies the Var interface.
type Float struct {
f uint64
}
func (v *Float) Value() float64 {
return math.Float64frombits(atomic.LoadUint64(&v.f))
}
func (v *Float) String() string {
return strconv.FormatFloat(
math.Float64frombits(atomic.LoadUint64(&v.f)),'g', -1, 64)
}
// Add adds delta to v.
func (v *Float) Add(delta float64) {
for {
cur := atomic.LoadUint64(&v.f)
curVal := math.Float64frombits(cur)
nxtVal := curVal + delta
nxt := math.Float64bits(nxtVal)
if atomic.CompareAndSwapUint64(&v.f,cur,nxt) {
return
}
}
}
// Set sets v to value.
func (v *Float) Set(value float64) {
atomic.StoreUint64(&v.f,math.Float64bits(value))
}
// Map is a string-to-Var map variable that satisfies the Var interface.
type Map struct {
m sync.Map // map[string]Var
keysMu sync.RWMutex
keys []string // sorted
}
// KeyValue represents a single entry in a Map.
type KeyValue struct {
Key string
Value Var
}
func (v *Map) String() string {
var b bytes.Buffer
fmt.Fprintf(&b,"{")
first := true
v.Do(func(kv KeyValue) {
if !first {
fmt.Fprintf(&b,",")
}
fmt.Fprintf(&b,"%q: %v",kv.Key,kv.Value)
first = false
})
fmt.Fprintf(&b,"}")
return b.String()
}
// Init removes all keys from the map.
func (v *Map) Init() *Map {
v.keysMu.Lock()
defer v.keysMu.Unlock()
v.keys = v.keys[:0]
v.m.Range(func(k,_ interface{}) bool {
v.m.Delete(k)
return true
})
return v
}
// updateKeys updates the sorted list of keys in v.keys.
func (v *Map) addKey(key string) {
v.keysMu.Lock()
defer v.keysMu.Unlock()
v.keys = append(v.keys,key)
sort.Strings(v.keys)
}
func (v *Map) Get(key string) Var {
i,_ := v.m.Load(key)
av,_ := i.(Var)
return av
}
func (v *Map) Set(key string,av Var) {
// Before we store the value,check to see whether the key is new. Try a Load
// before LoadOrStore: LoadOrStore causes the key interface to escape even on
// the Load path.
if _,ok := v.m.Load(key); !ok {
if _,dup := v.m.LoadOrStore(key,av); !dup {
v.addKey(key)
return
}
}
v.m.Store(key,av)
}
// Add adds delta to the *Int value stored under the given map key.
func (v *Map) Add(key string,delta int64) {
i,ok := v.m.Load(key)
if !ok {
var dup bool
i,dup = v.m.LoadOrStore(key,new(Int))
if !dup {
v.addKey(key)
}
}
// Add to Int; ignore otherwise.
if iv,ok := i.(*Int); ok {
iv.Add(delta)
}
}
// AddFloat adds delta to the *Float value stored under the given map key.
func (v *Map) AddFloat(key string,delta float64) {
i,new(Float))
if !dup {
v.addKey(key)
}
}
// Add to Float; ignore otherwise.
if iv,ok := i.(*Float); ok {
iv.Add(delta)
}
}
// Do calls f for each entry in the map.
// The map is locked during the iteration,
// but existing entries may be concurrently updated.
func (v *Map) Do(f func(KeyValue)) {
v.keysMu.RLock()
defer v.keysMu.RUnlock()
for _,k := range v.keys {
i,_ := v.m.Load(k)
f(KeyValue{k,i.(Var)})
}
}
// String is a string variable,and satisfies the Var interface.
type String struct {
s atomic.Value // string
}
func (v *String) Value() string {
p,_ := v.s.Load().(string)
return p
}
// String implements the Val interface. To get the unquoted string
// use Value.
func (v *String) String() string {
s := v.Value()
b,_ := json.Marshal(s)
return string(b)
}
func (v *String) Set(value string) {
v.s.Store(value)
}
// Func implements Var by calling the function
// and formatting the returned value using JSON.
type Func func() interface{}
func (f Func) Value() interface{} {
return f()
}
func (f Func) String() string {
v,_ := json.Marshal(f())
return string(v)
}
// All published variables.
var (
vars sync.Map // map[string]Var
varKeysMu sync.RWMutex
varKeys []string // sorted
)
// Publish declares a named exported variable. This should be called from a
// package's init function when it creates its Vars. If the name is already
// registered then this will log.Panic.
func Publish(name string,v Var) {
if _,dup := vars.LoadOrStore(name,v); dup {
log.Panicln("Reuse of exported var name:",name)
}
varKeysMu.Lock()
defer varKeysMu.Unlock()
varKeys = append(varKeys,name)
sort.Strings(varKeys)
}
// Get retrieves a named exported variable. It returns nil if the name has
// not been registered.
func Get(name string) Var {
i,_ := vars.Load(name)
v,_ := i.(Var)
return v
}
// Convenience functions for creating new exported variables.
func NewInt(name string) *Int {
v := new(Int)
Publish(name,v)
return v
}
func NewFloat(name string) *Float {
v := new(Float)
Publish(name,v)
return v
}
func NewMap(name string) *Map {
v := new(Map).Init()
Publish(name,v)
return v
}
func NewString(name string) *String {
v := new(String)
Publish(name,v)
return v
}
// Do calls f for each exported variable.
// The global variable map is locked during the iteration,
// but existing entries may be concurrently updated.
func Do(f func(KeyValue)) {
varKeysMu.RLock()
defer varKeysMu.RUnlock()
for _,k := range varKeys {
val,_ := vars.Load(k)
f(KeyValue{k,val.(Var)})
}
}
func expvarHandler(w http.ResponseWriter,r *http.Request) {
w.Header().Set("Content-Type","application/json; charset=utf-8")
fmt.Fprintf(w,"{\n")
first := true
Do(func(kv KeyValue) {
if !first {
fmt.Fprintf(w,\n")
}
first = false
fmt.Fprintf(w,"%q: %s",kv.Value)
})
fmt.Fprintf(w,"\n}\n")
}
// Handler returns the expvar HTTP Handler.
//
// This is only needed to install the handler in a non-standard location.
func Handler() http.Handler {
return http.HandlerFunc(expvarHandler)
}
func cmdline() interface{} {
return os.Args
}
func memstats() interface{} {
stats := new(runtime.MemStats)
runtime.ReadMemStats(stats)
return *stats
}
func init() {
http.HandleFunc("/debug/vars",expvarHandler)
Publish("cmdline",Func(cmdline))
Publish("memstats",Func(memstats))
}
简单应用
例1
main.go
package main
import (
"expvar"
"net"
"net/http"
)
var (
test = expvar.NewMap("Test")
)
func init() {
test.Add("go", 10)
test.Add("go1", 10)
}
func main() {
sock,err := net.Listen("tcp","localhost:8080")
if err != nil {
panic("error")
}
go func() {
http.Serve(sock,nil)
}()
select {}
}
浏览器访问:http://localhost:8080/debug/vars
例2
main.go
package main
import (
"expvar"
"io"
"net/http"
"strconv"
"time"
"github.com/paulbellamy/ratecounter"
)
var (
counter *ratecounter.RateCounter
hitsperminute = expvar.NewInt("hits_per_minute")
)
func increment(w http.ResponseWriter,r *http.Request) {
counter.Incr(1)
hitsperminute.Set(counter.Rate())
io.WriteString(w,strconv.FormatInt(counter.Rate(), 10))
}
func main() {
counter = ratecounter.NewRateCounter(1 * time.Minute)
http.HandleFunc("/increment",increment)
http.ListenAndServe(":8000",nil)
}
浏览器输入:http://localhost:8000/increment
curl命令行访问: curl http://localhost:8000/increment
浏览器访问:http://localhost:8000/debug/vars
完美例子
https://golang.org/src/net/http/triv.go?m=text
延伸
https://github.com/divan/expvarmon 之后会有详细介绍