diff --git a/Dockerfile b/Dockerfile index 96192fa..7538c35 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,19 +14,13 @@ RUN make build # -------------------------------------------------- -FROM alpine AS alp +FROM alpine AS tools RUN apk add go ENV GOPATH /go RUN go install github.com/tkuchiki/alp/cli/alp@latest - -# -------------------------------------------------- - -FROM alpine AS percona-toolkit - -RUN wget https://downloads.percona.com/downloads/percona-toolkit/3.4.0/binary/tarball/percona-toolkit-3.4.0_x86_64.tar.gz -RUN tar zxvf percona-toolkit-3.4.0_x86_64.tar.gz +RUN go install github.com/tkuchiki/slp/cmd/slp@latest # -------------------------------------------------- @@ -36,8 +30,8 @@ RUN apk add --no-cache bash perl perl-dbd-mysql perl-time-hires graphviz COPY --from=pprotein /go/src/app/pprotein /usr/local/bin/ COPY --from=pprotein /go/src/app/pprotein-agent /usr/local/bin/ -COPY --from=alp /go/bin/alp /usr/local/bin/ -COPY --from=percona-toolkit /percona-toolkit-3.4.0/bin/* /usr/local/bin/ +COPY --from=tools /go/bin/alp /usr/local/bin/ +COPY --from=tools /go/bin/slp /usr/local/bin/ RUN mkdir -p /opt/pprotein WORKDIR /opt/pprotein diff --git a/cli/pprotein/main.go b/cli/pprotein/main.go index d0014f6..4f25dcb 100644 --- a/cli/pprotein/main.go +++ b/cli/pprotein/main.go @@ -9,7 +9,7 @@ import ( "github.com/kaz/pprotein/internal/collect/group" "github.com/kaz/pprotein/internal/event" "github.com/kaz/pprotein/internal/extproc/alp" - "github.com/kaz/pprotein/internal/extproc/querydigest" + "github.com/kaz/pprotein/internal/extproc/slp" "github.com/kaz/pprotein/internal/memo" "github.com/kaz/pprotein/internal/pprof" "github.com/kaz/pprotein/internal/storage" @@ -47,47 +47,51 @@ func start() error { hub := event.NewHub() hub.RegisterHandlers(api.Group("/event")) - pOpts := &collect.Options{ + pprofOpts := &collect.Options{ Type: "pprof", Ext: "-pprof.pb.gz", Store: store, EventHub: hub, } - if err := pprof.NewHandler(pOpts).Register(api.Group("/pprof")); err != nil { + if err := pprof.NewHandler(pprofOpts).Register(api.Group("/pprof")); err != nil { return err } - aOpts := &collect.Options{ + alpOpts := &collect.Options{ Type: "httplog", Ext: "-httplog.log", Store: store, EventHub: hub, } - aHandler, err := alp.NewHandler(aOpts, store) + alpHandler, err := alp.NewHandler(alpOpts, store) if err != nil { return err } - if err := aHandler.Register(api.Group("/httplog")); err != nil { + if err := alpHandler.Register(api.Group("/httplog")); err != nil { return err } - qOpts := &collect.Options{ + slpOpts := &collect.Options{ Type: "slowlog", Ext: "-slowlog.log", Store: store, EventHub: hub, } - if err := querydigest.NewHandler(qOpts).Register(api.Group("/slowlog")); err != nil { + slpHandler, err := slp.NewHandler(slpOpts, store) + if err != nil { + return err + } + if err := slpHandler.Register(api.Group("/slowlog")); err != nil { return err } - mOpts := &collect.Options{ + memoOpts := &collect.Options{ Type: "memo", Ext: "-memo.log", Store: store, EventHub: hub, } - if err := memo.NewHandler(mOpts).Register(api.Group("/memo")); err != nil { + if err := memo.NewHandler(memoOpts).Register(api.Group("/memo")); err != nil { return err } diff --git a/internal/extproc/querydigest/handler.go b/internal/extproc/querydigest/handler.go deleted file mode 100644 index 1be8cda..0000000 --- a/internal/extproc/querydigest/handler.go +++ /dev/null @@ -1,26 +0,0 @@ -package querydigest - -import ( - "fmt" - - "github.com/kaz/pprotein/internal/collect" - "github.com/kaz/pprotein/internal/extproc" - "github.com/labstack/echo/v4" -) - -type ( - handler struct { - opts *collect.Options - } -) - -func NewHandler(opts *collect.Options) *handler { - return &handler{opts: opts} -} - -func (h *handler) Register(g *echo.Group) error { - if err := extproc.NewHandler(&processor{}, h.opts).Register(g); err != nil { - return fmt.Errorf("failed to register extproc handlers: %w", err) - } - return nil -} diff --git a/internal/extproc/slp/handler.go b/internal/extproc/slp/handler.go new file mode 100644 index 0000000..044d9c4 --- /dev/null +++ b/internal/extproc/slp/handler.go @@ -0,0 +1,59 @@ +package slp + +import ( + _ "embed" + "fmt" + + "github.com/kaz/pprotein/internal/collect" + "github.com/kaz/pprotein/internal/extproc" + "github.com/kaz/pprotein/internal/persistent" + "github.com/kaz/pprotein/internal/storage" + "github.com/labstack/echo/v4" + "gopkg.in/yaml.v3" +) + +type ( + handler struct { + opts *collect.Options + config *persistent.Handler + } +) + +//go:embed slp.yml +var defaultConfig []byte + +func NewHandler(opts *collect.Options, store storage.Storage) (*handler, error) { + h := &handler{ + opts: opts, + } + + config, err := persistent.New(store, "slp.yml", defaultConfig, h.sanitize) + if err != nil { + return nil, fmt.Errorf("failed to create targets: %w", err) + } + h.config = config + + return h, nil +} + +func (h *handler) Register(g *echo.Group) error { + h.config.RegisterHandlers(g.Group("/config")) + + if err := extproc.NewHandler(&processor{confPath: h.config.GetPath()}, h.opts).Register(g); err != nil { + return fmt.Errorf("failed to register extproc handlers: %w", err) + } + return nil +} + +func (h *handler) sanitize(raw []byte) ([]byte, error) { + var config interface{} + if err := yaml.Unmarshal(raw, &config); err != nil { + return nil, fmt.Errorf("failed to unmarshal: %w", err) + } + + res, err := yaml.Marshal(config) + if err != nil { + return nil, fmt.Errorf("failed to marshal: %w", err) + } + return res, nil +} diff --git a/internal/extproc/querydigest/processor.go b/internal/extproc/slp/processor.go similarity index 68% rename from internal/extproc/querydigest/processor.go rename to internal/extproc/slp/processor.go index e8e5b6e..d735250 100644 --- a/internal/extproc/querydigest/processor.go +++ b/internal/extproc/slp/processor.go @@ -1,4 +1,4 @@ -package querydigest +package slp import ( "bytes" @@ -10,7 +10,9 @@ import ( ) type ( - processor struct{} + processor struct { + confPath string + } ) func (p *processor) Cacheable() bool { @@ -23,13 +25,12 @@ func (p *processor) Process(snapshot *collect.Snapshot) (io.ReadCloser, error) { return nil, fmt.Errorf("failed to find snapshot body: %w", err) } - cmd := exec.Command("pt-query-digest", "--limit", "100%", "--output", "json", bodyPath) + cmd := exec.Command("slp", "--config", p.confPath, "--output", "standard", "--format", "tsv", "--file", bodyPath) res, err := cmd.Output() if err != nil { return nil, fmt.Errorf("external process aborted: %w", err) } - starts := bytes.IndexByte(res, '{') - return io.NopCloser(bytes.NewBuffer(res[starts:])), nil + return io.NopCloser(bytes.NewBuffer(res)), nil } diff --git a/internal/extproc/slp/slp.yml b/internal/extproc/slp/slp.yml new file mode 100644 index 0000000..5aba2a4 --- /dev/null +++ b/internal/extproc/slp/slp.yml @@ -0,0 +1,4 @@ +bundle_values: true +bundle_where_in: true +filters: Query matches "^(SELECT|INSERT|UPDATE|REPLACE) " +limit: 65536 diff --git a/view/src/components/HttpLogEntry.vue b/view/src/components/HttpLogEntry.vue index aac5cee..2e0041e 100644 --- a/view/src/components/HttpLogEntry.vue +++ b/view/src/components/HttpLogEntry.vue @@ -1,142 +1,23 @@ - - diff --git a/view/src/components/SlowLogEntry.vue b/view/src/components/SlowLogEntry.vue index 23a890f..faf4d12 100644 --- a/view/src/components/SlowLogEntry.vue +++ b/view/src/components/SlowLogEntry.vue @@ -1,221 +1,23 @@ - - diff --git a/view/src/components/TsvTable.vue b/view/src/components/TsvTable.vue new file mode 100644 index 0000000..203437c --- /dev/null +++ b/view/src/components/TsvTable.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/view/src/query-digest.d.ts b/view/src/query-digest.d.ts deleted file mode 100644 index 842e9a1..0000000 --- a/view/src/query-digest.d.ts +++ /dev/null @@ -1,94 +0,0 @@ -export interface QueryDigest { - classes: Class[]; - global: Global; -} - -export interface Class { - attribute: string; - checksum: string; - distillate: string; - example: Example; - fingerprint: string; - histograms: Histograms; - metrics: Metrics; - query_count: number; - tables?: Table[]; - ts_max: string; - ts_min: string; -} - -export interface Example { - Query_time: string; - query: string; - ts: string; - as_select?: string; -} - -export interface Histograms { - Query_time: number[]; -} - -export interface Metrics { - Lock_time: MetricsRow; - Query_length: MetricsRow; - Query_time: MetricsRow; - Rows_examined: MetricsRow; - Rows_sent: MetricsRow; - // 使わない & 型合わせのため - // host: Host; - // user: User; -} - -export interface MetricsRow { - avg: string; - max: string; - median: string; - min: string; - pct: string; - pct_95: string; - stddev: string; - sum: string; -} - -export interface Host { - value: string; -} - -export interface User { - value: string; -} - -export interface Table { - create: string; - status: string; -} - -export interface Global { - files: File[]; - metrics: Metrics2; - query_count: number; - unique_query_count: number; -} - -export interface File { - name: string; - size: number; -} - -export interface Metrics2 { - Lock_time: MetricsRow2; - Query_length: MetricsRow2; - Query_time: MetricsRow2; - Rows_examined: MetricsRow2; - Rows_sent: MetricsRow2; -} - -export interface MetricsRow2 { - avg: string; - max: string; - median: string; - min: string; - pct_95: string; - stddev: string; - sum: string; -} diff --git a/view/src/store.ts b/view/src/store.ts index 6de3349..fc2313d 100644 --- a/view/src/store.ts +++ b/view/src/store.ts @@ -43,7 +43,7 @@ const state = { groups: [] as string[], entries: {} as { [key: string]: Entry }, - settingKeys: ["group/targets", "httplog/config"], + settingKeys: ["group/targets", "httplog/config", "slowlog/config"], settings: {} as { [key: string]: SettingRecord }, };