|
| 1 | +# golang-sample-with-prometheus |
| 2 | + |
| 3 | +This repository is to demo how to use prometheus to collect golang application data |
| 4 | + |
| 5 | +## logic |
| 6 | +```golang |
| 7 | +package application |
| 8 | + |
| 9 | +import ( |
| 10 | + "encoding/json" |
| 11 | + "math/rand" |
| 12 | + "net/http" |
| 13 | + "strconv" |
| 14 | + "strings" |
| 15 | + "time" |
| 16 | + |
| 17 | + "github.com/leetcode-golang-classroom/golang-sample-with-prometheus/internal/config" |
| 18 | + "github.com/prometheus/client_golang/prometheus" |
| 19 | + "github.com/prometheus/client_golang/prometheus/collectors" |
| 20 | + "github.com/prometheus/client_golang/prometheus/promhttp" |
| 21 | +) |
| 22 | + |
| 23 | +type Device struct { |
| 24 | + ID int `json:"id"` |
| 25 | + Mac string `json:"mac"` |
| 26 | + Firmware string `json:"firmware"` |
| 27 | +} |
| 28 | + |
| 29 | +type registerDevicesHandler struct { |
| 30 | + metrics *metrics |
| 31 | +} |
| 32 | + |
| 33 | +type metrics struct { |
| 34 | + devices prometheus.Gauge |
| 35 | + info *prometheus.GaugeVec |
| 36 | + upgrades *prometheus.CounterVec |
| 37 | + duration *prometheus.HistogramVec |
| 38 | + loginDuration prometheus.Summary |
| 39 | +} |
| 40 | + |
| 41 | +var devices []Device |
| 42 | +var version string |
| 43 | + |
| 44 | +func init() { |
| 45 | + version = config.AppConfig.Version |
| 46 | + devices = []Device{ |
| 47 | + {1, "5F-33-CC-1F-43-82", "2.1.6"}, |
| 48 | + {2, "EF-2B-C4-F5-D6-34", "2.1.6"}, |
| 49 | + } |
| 50 | +} |
| 51 | + |
| 52 | +func (app *App) SetupMetricRoute() { |
| 53 | + reg := prometheus.NewRegistry() |
| 54 | + reg.MustRegister(collectors.NewGoCollector()) |
| 55 | + m := NewMetrics(reg) |
| 56 | + m.devices.Set(float64(len(devices))) |
| 57 | + m.info.With(prometheus.Labels{"version": version}).Set(1) |
| 58 | + |
| 59 | + promHandler := promhttp.HandlerFor(reg, promhttp.HandlerOpts{ |
| 60 | + Registry: reg, |
| 61 | + }) |
| 62 | + app.metricsRouter.Handle("/metrics", promHandler) |
| 63 | + rdh := registerDevicesHandler{metrics: m} |
| 64 | + mdh := manageDevicesHandler{metrics: m} |
| 65 | + lh := loginHandler{} |
| 66 | + mlh := middleware(lh, m) |
| 67 | + app.deviceRouter.Handle("/devices", rdh) |
| 68 | + app.deviceRouter.Handle("/devices/", mdh) |
| 69 | + app.deviceRouter.Handle("/login", mlh) |
| 70 | +} |
| 71 | + |
| 72 | +func getDevices(w http.ResponseWriter, _ *http.Request, m *metrics) { |
| 73 | + now := time.Now() |
| 74 | + b, err := json.Marshal(devices) |
| 75 | + if err != nil { |
| 76 | + http.Error(w, err.Error(), http.StatusBadRequest) |
| 77 | + return |
| 78 | + } |
| 79 | + sleep(200) |
| 80 | + |
| 81 | + m.duration.With(prometheus.Labels{"method": "GET", "status": "200"}).Observe(time.Since(now).Seconds()) |
| 82 | + w.Header().Set("Content-Type", "application/json") |
| 83 | + w.WriteHeader(http.StatusOK) |
| 84 | + w.Write(b) |
| 85 | +} |
| 86 | + |
| 87 | +func NewMetrics(reg prometheus.Registerer) *metrics { |
| 88 | + m := &metrics{ |
| 89 | + devices: prometheus.NewGauge(prometheus.GaugeOpts{ |
| 90 | + Namespace: "myapp", |
| 91 | + Name: "connected_devices", |
| 92 | + Help: "Number of currently connected devices", |
| 93 | + }), |
| 94 | + info: prometheus.NewGaugeVec(prometheus.GaugeOpts{ |
| 95 | + Namespace: "myapp", |
| 96 | + Name: "info", |
| 97 | + Help: "Information about the My App enviroment", |
| 98 | + }, []string{"version"}), |
| 99 | + upgrades: prometheus.NewCounterVec(prometheus.CounterOpts{ |
| 100 | + Namespace: "myapp", |
| 101 | + Name: "device_upgrade_total", |
| 102 | + Help: "Number of upgraded devices", |
| 103 | + }, []string{"type"}), |
| 104 | + duration: prometheus.NewHistogramVec(prometheus.HistogramOpts{ |
| 105 | + Namespace: "myapp", |
| 106 | + Name: "request_duration_seconds", |
| 107 | + Help: "Duration of the request.", |
| 108 | + // 4 times larger of apdex score |
| 109 | + // Buckets: prometheus.ExponentialBuckets(0.1, 1.5, 5), |
| 110 | + // Buckets: prometheus.LinearBuckets(0.1, 5, 5), |
| 111 | + Buckets: []float64{0.1, 0.15, 0.2, 0.25, 0.3}, |
| 112 | + }, []string{"status", "method"}), |
| 113 | + loginDuration: prometheus.NewSummary(prometheus.SummaryOpts{ |
| 114 | + Namespace: "myapp", |
| 115 | + Name: "login_request_duration_seconds", |
| 116 | + Help: "Duration of the login request", |
| 117 | + Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, |
| 118 | + }), |
| 119 | + } |
| 120 | + reg.MustRegister(m.devices, m.info, m.upgrades, m.duration, m.loginDuration) |
| 121 | + return m |
| 122 | +} |
| 123 | + |
| 124 | +func (rdh registerDevicesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
| 125 | + switch r.Method { |
| 126 | + case "GET": |
| 127 | + getDevices(w, r, rdh.metrics) |
| 128 | + case "POST": |
| 129 | + createDevice(w, r, rdh.metrics) |
| 130 | + default: |
| 131 | + w.Header().Set("Allow", "GET, POST") |
| 132 | + http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) |
| 133 | + } |
| 134 | + |
| 135 | +} |
| 136 | + |
| 137 | +func createDevice(w http.ResponseWriter, r *http.Request, m *metrics) { |
| 138 | + var device Device |
| 139 | + |
| 140 | + err := json.NewDecoder(r.Body).Decode(&device) |
| 141 | + if err != nil { |
| 142 | + http.Error(w, err.Error(), http.StatusBadRequest) |
| 143 | + return |
| 144 | + } |
| 145 | + |
| 146 | + devices = append(devices, device) |
| 147 | + m.devices.Set(float64(len(devices))) |
| 148 | + |
| 149 | + w.WriteHeader(http.StatusCreated) |
| 150 | + w.Write([]byte("Device created!")) |
| 151 | +} |
| 152 | + |
| 153 | +func upgradeDevice(w http.ResponseWriter, r *http.Request, m *metrics) { |
| 154 | + path := strings.TrimPrefix(r.URL.Path, "/devices/") |
| 155 | + |
| 156 | + id, err := strconv.Atoi(path) |
| 157 | + if err != nil || id < 1 { |
| 158 | + http.NotFound(w, r) |
| 159 | + return |
| 160 | + } |
| 161 | + |
| 162 | + var device Device |
| 163 | + err = json.NewDecoder(r.Body).Decode(&device) |
| 164 | + if err != nil { |
| 165 | + http.Error(w, err.Error(), http.StatusBadRequest) |
| 166 | + return |
| 167 | + } |
| 168 | + |
| 169 | + for i := range devices { |
| 170 | + if devices[i].ID == id { |
| 171 | + devices[i].Firmware = device.Firmware |
| 172 | + } |
| 173 | + } |
| 174 | + |
| 175 | + sleep(1000) |
| 176 | + |
| 177 | + m.upgrades.With(prometheus.Labels{"type": "router"}).Inc() |
| 178 | + w.WriteHeader(http.StatusAccepted) |
| 179 | + w.Write([]byte("Upgrading...")) |
| 180 | +} |
| 181 | + |
| 182 | +type manageDevicesHandler struct { |
| 183 | + metrics *metrics |
| 184 | +} |
| 185 | + |
| 186 | +func (mdh manageDevicesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
| 187 | + switch r.Method { |
| 188 | + case "PUT": |
| 189 | + upgradeDevice(w, r, mdh.metrics) |
| 190 | + default: |
| 191 | + w.Header().Set("Allow", "PUT") |
| 192 | + http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) |
| 193 | + } |
| 194 | +} |
| 195 | + |
| 196 | +func sleep(ms int) { |
| 197 | + randGenerator := rand.New(rand.NewSource(time.Now().UnixNano())) |
| 198 | + now := time.Now() |
| 199 | + n := randGenerator.Intn(ms + now.Second()) |
| 200 | + time.Sleep(time.Duration(n) * time.Millisecond) |
| 201 | +} |
| 202 | + |
| 203 | +type loginHandler struct{} |
| 204 | + |
| 205 | +func (l loginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
| 206 | + sleep(200) |
| 207 | + w.Write([]byte("Welcome to the app!")) |
| 208 | +} |
| 209 | + |
| 210 | +func middleware(next http.Handler, m *metrics) http.Handler { |
| 211 | + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 212 | + now := time.Now() |
| 213 | + next.ServeHTTP(w, r) |
| 214 | + m.loginDuration.Observe(time.Since(now).Seconds()) |
| 215 | + }) |
| 216 | +} |
| 217 | + |
| 218 | +``` |
| 219 | +## metric this sample could build |
| 220 | + |
| 221 | + |
0 commit comments