X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=server%2Fquery.go;h=22385bf07de13e576e03007b179ede5fd6e97000;hb=ed155b476ed90127d81de598b6d4a685ed76f2b3;hp=cb74f22f479dcd4bbec5dcd787b82f0fdb992c5c;hpb=ab1f53c34612dc816a00055c283884bf316e5f1c;p=sysdb%2Fwebui.git diff --git a/server/query.go b/server/query.go index cb74f22..22385bf 100644 --- a/server/query.go +++ b/server/query.go @@ -30,13 +30,12 @@ package server import ( "errors" "fmt" - "log" "strings" "time" "unicode" + "github.com/sysdb/go/client" "github.com/sysdb/go/proto" - "github.com/sysdb/go/sysdb" ) func listAll(req request, s *Server) (*page, error) { @@ -44,7 +43,11 @@ func listAll(req request, s *Server) (*page, error) { return nil, fmt.Errorf("%s not found", strings.Title(req.cmd)) } - res, err := s.query("LIST %s", identifier(req.cmd)) + q, err := client.QueryString("LIST %s", client.Identifier(req.cmd)) + if err != nil { + return nil, err + } + res, err := s.c.Query(q) if err != nil { return nil, err } @@ -56,52 +59,39 @@ func lookup(req request, s *Server) (*page, error) { if req.r.Method != "POST" { return nil, errors.New("Method not allowed") } - tokens, err := tokenize(req.r.PostForm.Get("query")) + raw, err := parseQuery(req.r.PostForm.Get("query")) if err != nil { return nil, err } - if len(tokens) == 0 { - return nil, errors.New("Empty query") - } - typ := "hosts" + if raw.typ == "" { + raw.typ = "hosts" + } var args string - for i, tok := range tokens { + for name, value := range raw.args { if len(args) > 0 { args += " AND" } - if fields := strings.SplitN(tok, ":", 2); len(fields) == 2 { - // Query: [:] [.]: ... - if i == 0 && fields[1] == "" { - typ = fields[0] - } else if elems := strings.Split(fields[0], "."); len(elems) > 1 { - objs := elems[:len(elems)-1] - for _, o := range objs { - if o != "host" && o != "service" && o != "metric" { - return nil, fmt.Errorf("Invalid object type %q", o) - } - } - args += fmt.Sprintf(" %s.attribute[%s] = %s", - strings.Join(objs, "."), proto.EscapeString(elems[len(elems)-1]), - proto.EscapeString(fields[1])) - } else { - args += fmt.Sprintf(" attribute[%s] = %s", - proto.EscapeString(fields[0]), proto.EscapeString(fields[1])) - } + if name == "name" { + args += fmt.Sprintf(" name =~ %s", value) } else { - args += fmt.Sprintf(" name =~ %s", proto.EscapeString(tok)) + args += fmt.Sprintf(" %s = %s", name, value) } } - res, err := s.query("LOOKUP %s MATCHING"+args, identifier(typ)) + q, err := client.QueryString("LOOKUP %s MATCHING"+args, client.Identifier(raw.typ)) + if err != nil { + return nil, err + } + res, err := s.c.Query(q) if err != nil { return nil, err } - if t, ok := s.results[typ]; ok { + if t, ok := s.results[raw.typ]; ok { return tmpl(t, res) } - return nil, fmt.Errorf("Unsupported type %s", typ) + return nil, fmt.Errorf("Unsupported type %s", raw.typ) } func fetch(req request, s *Server) (*page, error) { @@ -109,31 +99,55 @@ func fetch(req request, s *Server) (*page, error) { return nil, fmt.Errorf("%s not found", strings.Title(req.cmd)) } - var res interface{} + var q string var err error switch req.cmd { case "host": if len(req.args) != 1 { return nil, fmt.Errorf("%s not found", strings.Title(req.cmd)) } - res, err = s.query("FETCH host %s", req.args[0]) + q, err = client.QueryString("FETCH host %s", req.args[0]) case "service", "metric": if len(req.args) != 2 { return nil, fmt.Errorf("%s not found", strings.Title(req.cmd)) } - res, err = s.query("FETCH %s %s.%s", identifier(req.cmd), req.args[0], req.args[1]) - if err == nil && req.cmd == "metric" { - return metric(req, res, s) - } + q, err = client.QueryString("FETCH %s %s.%s", client.Identifier(req.cmd), req.args[0], req.args[1]) default: panic("Unknown request: fetch(" + req.cmd + ")") } if err != nil { return nil, err } + + res, err := s.c.Query(q) + if err != nil { + return nil, err + } + if req.cmd == "metric" { + return metric(req, res, s) + } return tmpl(s.results[req.cmd], res) } +func graphs(req request, s *Server) (*page, error) { + p := struct { + Query, Metrics string + QueryOptions string + GroupBy string + }{ + Query: req.r.PostForm.Get("metrics-query"), + GroupBy: req.r.PostForm.Get("group-by"), + } + + if req.r.Method == "POST" { + p.Metrics = p.Query + if p.GroupBy != "" { + p.QueryOptions += "/g=" + strings.Join(strings.Fields(p.GroupBy), ",") + } + } + return tmpl(s.results["graphs"], &p) +} + var datetime = "2006-01-02 15:04:05" func metric(req request, res interface{}, s *Server) (*page, error) { @@ -170,6 +184,69 @@ func metric(req request, res interface{}, s *Server) (*page, error) { return tmpl(s.results["metric"], &p) } +type query struct { + typ string + args map[string]string +} + +func (q *query) arg(name, value string) error { + if _, ok := q.args[name]; ok { + return fmt.Errorf("Duplicate key %q", name) + } + q.args[name] = proto.EscapeString(value) + return nil +} + +func (q *query) attr(parent, name, value string) error { + var k string + if parent != "" { + k = fmt.Sprintf("%s.attribute[%s]", parent, proto.EscapeString(name)) + } else { + k = fmt.Sprintf("attribute[%s]", proto.EscapeString(name)) + } + + return q.arg(k, value) +} + +func parseQuery(s string) (*query, error) { + tokens, err := tokenize(s) + if err != nil { + return nil, err + } + if len(tokens) == 0 { + return nil, errors.New("Empty query") + } + + q := &query{args: make(map[string]string)} + for i, tok := range tokens { + if fields := strings.SplitN(tok, ":", 2); len(fields) == 2 { + // Query: [:] [.]: ... + if i == 0 && fields[1] == "" { + q.typ = fields[0] + } else if elems := strings.Split(fields[0], "."); len(elems) > 1 { + objs := elems[:len(elems)-1] + for _, o := range objs { + if o != "host" && o != "service" && o != "metric" { + return nil, fmt.Errorf("Invalid object type %q", o) + } + } + if err := q.attr(strings.Join(objs, "."), elems[len(elems)-1], fields[1]); err != nil { + return nil, err + } + } else { + if err := q.attr("", fields[0], fields[1]); err != nil { + return nil, err + } + } + } else { + if err := q.arg("name", tok); err != nil { + return nil, err + } + } + } + return q, nil +} + // tokenize split the string s into its tokens where a token is either a quoted // string or surrounded by one or more consecutive whitespace characters. func tokenize(s string) ([]string, error) { @@ -267,73 +344,4 @@ func (s *scanner) inField(r rune) bool { return !unicode.IsSpace(r) } -type identifier string - -func (s *Server) query(cmd string, args ...interface{}) (interface{}, error) { - c := <-s.conns - defer func() { s.conns <- c }() - - for i, arg := range args { - switch v := arg.(type) { - case identifier: - // Nothing to do. - case string: - args[i] = proto.EscapeString(v) - case time.Time: - args[i] = v.Format(datetime) - default: - panic(fmt.Sprintf("query: invalid type %T", arg)) - } - } - - cmd = fmt.Sprintf(cmd, args...) - m := &proto.Message{ - Type: proto.ConnectionQuery, - Raw: []byte(cmd), - } - if err := c.Send(m); err != nil { - return nil, fmt.Errorf("Query %q: %v", cmd, err) - } - - for { - m, err := c.Receive() - if err != nil { - return nil, fmt.Errorf("Failed to receive server response: %v", err) - } - if m.Type == proto.ConnectionLog { - log.Println(string(m.Raw[4:])) - continue - } else if m.Type == proto.ConnectionError { - return nil, errors.New(string(m.Raw)) - } - - t, err := m.DataType() - if err != nil { - return nil, fmt.Errorf("Failed to unmarshal response: %v", err) - } - - var res interface{} - switch t { - case proto.HostList: - var hosts []sysdb.Host - err = proto.Unmarshal(m, &hosts) - res = hosts - case proto.Host: - var host sysdb.Host - err = proto.Unmarshal(m, &host) - res = host - case proto.Timeseries: - var ts sysdb.Timeseries - err = proto.Unmarshal(m, &ts) - res = ts - default: - return nil, fmt.Errorf("Unsupported data type %d", t) - } - if err != nil { - return nil, fmt.Errorf("Failed to unmarshal response: %v", err) - } - return res, nil - } -} - // vim: set tw=78 sw=4 sw=4 noexpandtab :