// ServeLogs registers the URL on which messages will be broadcast. // It is safe to register multiple URLs for the same StreamLogger. func (logger *StreamLogger) ServeLogs(url string, messageFmt func(url.Values, interface{}) string) { http.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) } ch := logger.Subscribe("ServeLogs") defer logger.Unsubscribe(ch) // Notify client that we're set up. Helpful to distinguish low-traffic streams from connection issues. w.WriteHeader(http.StatusOK) w.(http.Flusher).Flush() for message := range ch { if _, err := io.WriteString(w, messageFmt(r.Form, message)); err != nil { return } w.(http.Flusher).Flush() } }) log.Infof("Streaming logs from %s at %v.", logger.Name(), url) }
func (cp *CachePool) ServeHTTP(response http.ResponseWriter, request *http.Request) { if err := acl.CheckAccessHTTP(request, acl.MONITORING); err != nil { acl.SendError(response, err) return } defer func() { if x := recover(); x != nil { response.Write(([]byte)(x.(error).Error())) } }() response.Header().Set("Content-Type", "text/plain") pool := cp.getPool() if pool == nil { response.Write(([]byte)("closed")) return } command := request.URL.Path[len(statsURL):] if command == "stats" { command = "" } conn := cp.Get() // This is not the same as defer rc.cachePool.Put(conn) defer func() { cp.Put(conn) }() r, err := conn.Stats(command) if err != nil { conn.Close() conn = nil response.Write(([]byte)(err.Error())) } else { response.Write(r) } }
func schemazHandler(tables []*schema.Table, w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } startHTMLTable(w) defer endHTMLTable(w) w.Write(schemazHeader) sorter := schemazSorter{ rows: tables, less: func(row1, row2 *schema.Table) bool { return row1.Name > row2.Name }, } sort.Sort(&sorter) envelope := struct { ColumnCategory []string CacheType []string Table *schema.Table }{ ColumnCategory: []string{"other", "number", "varbinary"}, CacheType: []string{"none", "read-write", "write-only"}, } for _, Value := range sorter.rows { envelope.Table = Value if err := schemazTmpl.Execute(w, envelope); err != nil { log.Errorf("schemaz: couldn't execute template: %v", err) } } }
func streamqueryzHandler(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } rows := SqlQueryRpcService.qe.streamQList.GetQueryzRows() if err := r.ParseForm(); err != nil { http.Error(w, fmt.Sprintf("cannot parse form: %s", err), http.StatusInternalServerError) return } format := r.FormValue("format") if format == "json" { js, err := json.Marshal(rows) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Write(js) return } startHTMLTable(w) defer endHTMLTable(w) w.Write(streamqueryzHeader) for i := range rows { streamqueryzTmpl.Execute(w, rows[i]) } }
// ServeHTTP shows the current plans in the query cache. func (plr *Planner) ServeHTTP(response http.ResponseWriter, request *http.Request) { if err := acl.CheckAccessHTTP(request, acl.DEBUGGING); err != nil { acl.SendError(response, err) return } if request.URL.Path == "/debug/query_plans" { keys := plr.plans.Keys() response.Header().Set("Content-Type", "text/plain") response.Write([]byte(fmt.Sprintf("Length: %d\n", len(keys)))) for _, v := range keys { response.Write([]byte(fmt.Sprintf("%#v\n", v))) if plan, ok := plr.plans.Peek(v); ok { if b, err := json.MarshalIndent(plan, "", " "); err != nil { response.Write([]byte(err.Error())) } else { response.Write(b) } response.Write(([]byte)("\n\n")) } } } else if request.URL.Path == "/debug/vschema" { response.Header().Set("Content-Type", "application/json; charset=utf-8") b, err := json.MarshalIndent(plr.VSchema().Keyspaces, "", " ") if err != nil { response.Write([]byte(err.Error())) return } buf := bytes.NewBuffer(nil) json.HTMLEscape(buf, b) response.Write(buf.Bytes()) } else { response.WriteHeader(http.StatusNotFound) } }
func schemazHandler(tables []*schema.Table, w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } logz.StartHTMLTable(w) defer logz.EndHTMLTable(w) w.Write(schemazHeader) sorter := schemazSorter{ rows: tables, less: func(row1, row2 *schema.Table) bool { return row1.Name > row2.Name }, } sort.Sort(&sorter) envelope := struct { Type []string Table *schema.Table }{ Type: schema.TypeNames, } for _, Value := range sorter.rows { envelope.Table = Value if err := schemazTmpl.Execute(w, envelope); err != nil { log.Errorf("schemaz: couldn't execute template: %v", err) } } }
func healthCheck(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.MONITORING); err != nil { acl.SendError(w, err) return } w.Header().Set("Content-Type", "text/plain") if err := IsHealthy(); err != nil { w.Write([]byte("notok")) } w.Write([]byte("ok")) }
func (tsv *TabletServer) registerDebugHealthHandler() { http.HandleFunc("/debug/health", func(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.MONITORING); err != nil { acl.SendError(w, err) return } w.Header().Set("Content-Type", "text/plain") if err := tsv.IsHealthy(); err != nil { w.Write([]byte("not ok")) return } w.Write([]byte("ok")) }) }
// querylogzHandler serves a human readable snapshot of the // current query log. func querylogzHandler(ch chan interface{}, w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } timeout, limit := parseTimeoutLimitParams(r) logz.StartHTMLTable(w) defer logz.EndHTMLTable(w) w.Write(querylogzHeader) tmr := time.NewTimer(timeout) defer tmr.Stop() for i := 0; i < limit; i++ { select { case out := <-ch: select { case <-tmr.C: return default: } stats, ok := out.(*LogStats) if !ok { err := fmt.Errorf("Unexpected value in %s: %#v (expecting value of type %T)", TxLogger.Name(), out, &LogStats{}) io.WriteString(w, `<tr class="error">`) io.WriteString(w, err.Error()) io.WriteString(w, "</tr>") log.Error(err) continue } var level string if stats.TotalTime().Seconds() < 0.01 { level = "low" } else if stats.TotalTime().Seconds() < 0.1 { level = "medium" } else { level = "high" } tmplData := struct { *LogStats ColorLevel string }{stats, level} if err := querylogzTmpl.Execute(w, tmplData); err != nil { log.Errorf("querylogz: couldn't execute template: %v", err) } case <-tmr.C: return } } }
// txlogzHandler serves a human readable snapshot of the // current transaction log. // Endpoint: /txlogz?timeout=%d&limit=%d // timeout: the txlogz will keep dumping transactions until timeout // limit: txlogz will keep dumping transcations until it hits the limit func txlogzHandler(w http.ResponseWriter, req *http.Request) { if err := acl.CheckAccessHTTP(req, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } timeout, limit := parseTimeoutLimitParams(req) ch := TxLogger.Subscribe("txlogz") defer TxLogger.Unsubscribe(ch) logz.StartHTMLTable(w) defer logz.EndHTMLTable(w) w.Write(txlogzHeader) tmr := time.NewTimer(timeout) defer tmr.Stop() for i := 0; i < limit; i++ { select { case out := <-ch: txc, ok := out.(*TxConnection) if !ok { err := fmt.Errorf("Unexpected value in %s: %#v (expecting value of type %T)", TxLogger.Name(), out, &TxConnection{}) io.WriteString(w, `<tr class="error">`) io.WriteString(w, err.Error()) io.WriteString(w, "</tr>") log.Error(err) continue } duration := txc.EndTime.Sub(txc.StartTime).Seconds() var level string if duration < 0.1 { level = "low" } else if duration < 1.0 { level = "medium" } else { level = "high" } tmplData := struct { *TxConnection Duration float64 ColorLevel string }{txc, duration, level} if err := txlogzTmpl.Execute(w, tmplData); err != nil { log.Errorf("txlogz: couldn't execute template: %v", err) } case <-tmr.C: return } } }
func queryzHandler(si *SchemaInfo, w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } startHTMLTable(w) defer endHTMLTable(w) w.Write(queryzHeader) keys := si.queries.Keys() sorter := queryzSorter{ rows: make([]*queryzRow, 0, len(keys)), less: func(row1, row2 *queryzRow) bool { return row1.timePQ() > row2.timePQ() }, } for _, v := range si.queries.Keys() { plan := si.peekQuery(v) if plan == nil { continue } Value := &queryzRow{ Query: wrappable(v), Table: plan.TableName, Plan: plan.PlanID, Reason: plan.Reason, } Value.Count, Value.tm, Value.Rows, Value.Errors = plan.Stats() var timepq time.Duration if Value.Count != 0 { timepq = time.Duration(int64(Value.tm) / Value.Count) } if timepq < 10*time.Millisecond { Value.Color = "low" } else if timepq < 100*time.Millisecond { Value.Color = "medium" } else { Value.Color = "high" } sorter.rows = append(sorter.rows, Value) } sort.Sort(&sorter) for _, Value := range sorter.rows { if err := queryzTmpl.Execute(w, Value); err != nil { log.Errorf("queryz: couldn't execute template: %v", err) } } }
func (co *Consolidator) ServeHTTP(response http.ResponseWriter, request *http.Request) { if err := acl.CheckAccessHTTP(request, acl.DEBUGGING); err != nil { acl.SendError(response, err) return } items := co.consolidations.Items() response.Header().Set("Content-Type", "text/plain") if items == nil { response.Write([]byte("empty\n")) return } response.Write([]byte(fmt.Sprintf("Length: %d\n", len(items)))) for _, v := range items { response.Write([]byte(fmt.Sprintf("%v: %s\n", v.Value.(*ccount).Get(), v.Key))) } }
func (si *SchemaInfo) ServeHTTP(response http.ResponseWriter, request *http.Request) { if err := acl.CheckAccessHTTP(request, acl.DEBUGGING); err != nil { acl.SendError(response, err) return } if ep, ok := si.endpoints[debugQueryPlansKey]; ok && request.URL.Path == ep { si.handleHTTPQueryPlans(response, request) } else if ep, ok := si.endpoints[debugQueryStatsKey]; ok && request.URL.Path == ep { si.handleHTTPQueryStats(response, request) } else if ep, ok := si.endpoints[debugTableStatsKey]; ok && request.URL.Path == ep { si.handleHTTPTableStats(response, request) } else if ep, ok := si.endpoints[debugSchemaKey]; ok && request.URL.Path == ep { si.handleHTTPSchema(response, request) } else { response.WriteHeader(http.StatusNotFound) } }
// txlogzHandler serves a human readable snapshot of the // current transaction log. func txlogzHandler(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } ch := TxLogger.Subscribe() defer TxLogger.Unsubscribe(ch) startHTMLTable(w) defer endHTMLTable(w) w.Write(txlogzHeader) deadline := time.After(10 * time.Second) for i := 0; i < 300; i++ { select { case out := <-ch: txc, ok := out.(*TxConnection) if !ok { err := fmt.Errorf("Unexpected value in %s: %#v (expecting value of type %T)", TxLogger.Name(), out, &TxConnection{}) io.WriteString(w, `<tr class="error">`) io.WriteString(w, err.Error()) io.WriteString(w, "</tr>") log.Error(err) continue } duration := txc.EndTime.Sub(txc.StartTime).Seconds() var level string if duration < 0.1 { level = "low" } else if duration < 1.0 { level = "medium" } else { level = "high" } tmplData := struct { *TxConnection Duration float64 ColorLevel string }{txc, duration, level} txlogzTmpl.Execute(w, tmplData) case <-deadline: return } } }
// queryzHandler displays the query stats. func queryzHandler(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } startHTMLTable(w) defer endHTMLTable(w) w.Write(queryzHeader) si := SqlQueryRpcService.qe.schemaInfo keys := si.queries.Keys() sorter := queryzSorter{ rows: make([]*queryzRow, 0, len(keys)), less: func(row1, row2 *queryzRow) bool { return row1.timePQ() > row2.timePQ() }, } for _, v := range si.queries.Keys() { plan := si.getQuery(v) if plan == nil { continue } Value := &queryzRow{ Query: wrappable(v), Table: plan.TableName, Plan: plan.PlanId, } Value.Count, Value.tm, Value.Rows, Value.Errors = plan.Stats() timepq := time.Duration(int64(Value.tm) / Value.Count) if timepq < 10*time.Millisecond { Value.Color = "low" } else if timepq < 100*time.Millisecond { Value.Color = "medium" } else { Value.Color = "high" } sorter.rows = append(sorter.rows, Value) } sort.Sort(&sorter) for _, Value := range sorter.rows { queryzTmpl.Execute(w, Value) } }
// querylogzHandler serves a human readable snapshot of the // current query log. func querylogzHandler(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } ch := SqlQueryLogger.Subscribe("querylogz") defer SqlQueryLogger.Unsubscribe(ch) startHTMLTable(w) defer endHTMLTable(w) w.Write(querylogzHeader) tmr := time.NewTimer(10 * time.Second) defer tmr.Stop() for i := 0; i < 300; i++ { select { case out := <-ch: stats, ok := out.(*SQLQueryStats) if !ok { err := fmt.Errorf("Unexpected value in %s: %#v (expecting value of type %T)", TxLogger.Name(), out, &SQLQueryStats{}) io.WriteString(w, `<tr class="error">`) io.WriteString(w, err.Error()) io.WriteString(w, "</tr>") log.Error(err) continue } var level string if stats.TotalTime().Seconds() < 0.01 { level = "low" } else if stats.TotalTime().Seconds() < 0.1 { level = "medium" } else { level = "high" } tmplData := struct { *SQLQueryStats ColorLevel string }{stats, level} querylogzTmpl.Execute(w, tmplData) case <-tmr.C: return } } }
// ServeLogs registers the URL on which messages will be broadcast. // It is safe to register multiple URLs for the same StreamLogger. func (logger *StreamLogger) ServeLogs(url string, messageFmt func(url.Values, interface{}) string) { http.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) } ch := logger.Subscribe() defer logger.Unsubscribe(ch) for message := range ch { if _, err := io.WriteString(w, messageFmt(r.Form, message)); err != nil { return } w.(http.Flusher).Flush() } }) log.Infof("Streaming logs from %s at %v.", logger.Name(), url) }
func streamqueryzTerminateHandler(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.ADMIN); err != nil { acl.SendError(w, err) return } if err := r.ParseForm(); err != nil { http.Error(w, fmt.Sprintf("cannot parse form: %s", err), http.StatusInternalServerError) return } connID := r.FormValue("connID") c, err := strconv.Atoi(connID) if err != nil { http.Error(w, "invalid connID", http.StatusInternalServerError) return } if err = SqlQueryRpcService.qe.streamQList.Terminate(int64(c)); err != nil { http.Error(w, fmt.Sprintf("error: %v", err), http.StatusInternalServerError) return } streamqueryzHandler(w, r) }
func statusHandler(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { acl.SendError(w, err) return } statusMu.Lock() defer statusMu.Unlock() data := struct { Sections []section BinaryName string Hostname string StartTime string }{ Sections: statusSections, BinaryName: binaryName, Hostname: hostname, StartTime: serverStart.Format(time.RFC1123), } if err := statusTmpl.ExecuteTemplate(w, "status", data); err != nil { log.Errorf("servenv: couldn't execute template: %v", err) } }
// InitStatusHandling installs webserver handlers for global actions like /status, /reset and /cancel. func (wi *Instance) InitStatusHandling() { // code to serve /status workerTemplate := mustParseTemplate("worker", workerStatusHTML) http.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.ADMIN); err != nil { acl.SendError(w, err) return } wi.currentWorkerMutex.Lock() wrk := wi.currentWorker logger := wi.currentMemoryLogger ctx := wi.currentContext err := wi.lastRunError wi.currentWorkerMutex.Unlock() data := make(map[string]interface{}) if wrk != nil { status := template.HTML("Current worker:<br>\n") + wrk.StatusAsHTML() if ctx == nil { data["Done"] = true if err != nil { status += template.HTML(fmt.Sprintf("<br>\nEnded with an error: %v<br>\n", err)) } } data["Status"] = status if logger != nil { data["Logs"] = template.HTML(strings.Replace(logger.String(), "\n", "</br>\n", -1)) } else { data["Logs"] = template.HTML("See console for logs</br>\n") } } executeTemplate(w, workerTemplate, data) }) // add the section in status that does auto-refresh of status div servenv.AddStatusPart("Worker Status", workerStatusPartHTML, func() interface{} { return nil }) // reset handler http.HandleFunc("/reset", func(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.ADMIN); err != nil { acl.SendError(w, err) return } if err := wi.Reset(); err != nil { httpError(w, err.Error(), nil) } else { // No worker currently running, we go to the menu. http.Redirect(w, r, "/", http.StatusTemporaryRedirect) } }) // cancel handler http.HandleFunc("/cancel", func(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.ADMIN); err != nil { acl.SendError(w, err) return } wi.currentWorkerMutex.Lock() // no worker, or not running, we go to the menu if wi.currentWorker == nil || wi.currentCancelFunc == nil { wi.currentWorkerMutex.Unlock() http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } // otherwise, cancel the running worker and go back to the status page cancel := wi.currentCancelFunc wi.currentWorkerMutex.Unlock() cancel() http.Redirect(w, r, servenv.StatusURLPath(), http.StatusTemporaryRedirect) }) }
func (si *SchemaInfo) ServeHTTP(response http.ResponseWriter, request *http.Request) { if err := acl.CheckAccessHTTP(request, acl.DEBUGGING); err != nil { acl.SendError(response, err) return } if request.URL.Path == "/debug/query_plans" { keys := si.queries.Keys() response.Header().Set("Content-Type", "text/plain") response.Write([]byte(fmt.Sprintf("Length: %d\n", len(keys)))) for _, v := range keys { response.Write([]byte(fmt.Sprintf("%#v\n", v))) if plan := si.getQuery(v); plan != nil { if b, err := json.MarshalIndent(plan.ExecPlan, "", " "); err != nil { response.Write([]byte(err.Error())) } else { response.Write(b) } response.Write(([]byte)("\n\n")) } } } else if request.URL.Path == "/debug/query_stats" { keys := si.queries.Keys() response.Header().Set("Content-Type", "application/json; charset=utf-8") qstats := make([]perQueryStats, 0, len(keys)) for _, v := range keys { if plan := si.getQuery(v); plan != nil { var pqstats perQueryStats pqstats.Query = unicoded(v) pqstats.Table = plan.TableName pqstats.Plan = plan.PlanId pqstats.QueryCount, pqstats.Time, pqstats.RowCount, pqstats.ErrorCount = plan.Stats() qstats = append(qstats, pqstats) } } if b, err := json.MarshalIndent(qstats, "", " "); err != nil { response.Write([]byte(err.Error())) } else { response.Write(b) } } else if request.URL.Path == "/debug/table_stats" { response.Header().Set("Content-Type", "application/json; charset=utf-8") tstats := make(map[string]struct{ hits, absent, misses, invalidations int64 }) var temp, totals struct{ hits, absent, misses, invalidations int64 } func() { si.mu.Lock() defer si.mu.Unlock() for k, v := range si.tables { if v.CacheType != schema.CACHE_NONE { temp.hits, temp.absent, temp.misses, temp.invalidations = v.Stats() tstats[k] = temp totals.hits += temp.hits totals.absent += temp.absent totals.misses += temp.misses totals.invalidations += temp.invalidations } } }() response.Write([]byte("{\n")) for k, v := range tstats { fmt.Fprintf(response, "\"%s\": {\"Hits\": %v, \"Absent\": %v, \"Misses\": %v, \"Invalidations\": %v},\n", k, v.hits, v.absent, v.misses, v.invalidations) } fmt.Fprintf(response, "\"Totals\": {\"Hits\": %v, \"Absent\": %v, \"Misses\": %v, \"Invalidations\": %v}\n", totals.hits, totals.absent, totals.misses, totals.invalidations) response.Write([]byte("}\n")) } else if request.URL.Path == "/debug/schema" { response.Header().Set("Content-Type", "application/json; charset=utf-8") tables := si.GetSchema() b, err := json.MarshalIndent(tables, "", " ") if err != nil { response.Write([]byte(err.Error())) return } buf := bytes.NewBuffer(nil) json.HTMLEscape(buf, b) response.Write(buf.Bytes()) } else { response.WriteHeader(http.StatusNotFound) } }
// InitInteractiveMode installs webserver handlers for each known command. func (wi *Instance) InitInteractiveMode() { indexTemplate := mustParseTemplate("index", indexHTML) subIndexTemplate := mustParseTemplate("subIndex", subIndexHTML) // toplevel menu http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.ADMIN); err != nil { acl.SendError(w, err) return } executeTemplate(w, indexTemplate, commands) }) // command group menus for _, cg := range commands { // keep a local copy of the Command pointer for the // closure. pcg := cg http.HandleFunc("/"+cg.Name, func(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.ADMIN); err != nil { acl.SendError(w, err) return } executeTemplate(w, subIndexTemplate, pcg) }) for _, c := range cg.Commands { // keep a local copy of the Command pointer for the closure. pc := c http.HandleFunc("/"+cg.Name+"/"+c.Name, func(w http.ResponseWriter, r *http.Request) { if err := acl.CheckAccessHTTP(r, acl.ADMIN); err != nil { acl.SendError(w, err) return } wrk, template, data, err := pc.Interactive(wi.backgroundContext, wi, wi.wr, w, r) if err != nil { httpError(w, "%s", err) } else if template != nil && data != nil { executeTemplate(w, template, data) return } if wrk == nil { httpError(w, "Internal server error. Command: %s did not return correct response.", c.Name) return } if _, err := wi.setAndStartWorker(wrk, wi.wr); err != nil { httpError(w, "Could not set %s worker: %s", c.Name, err) return } http.Redirect(w, r, servenv.StatusURLPath(), http.StatusTemporaryRedirect) }) } } log.Infof("Interactive mode ready") }