Skip to content

Commit

Permalink
feat: detect url status
Browse files Browse the repository at this point in the history
  • Loading branch information
clement2026 committed Aug 23, 2024
1 parent 08261de commit 46359bf
Show file tree
Hide file tree
Showing 27 changed files with 531 additions and 120 deletions.
14 changes: 14 additions & 0 deletions .github/workflows/template/setup-go-template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
name: Setup go
on: [ workflow_call ]

jobs:
- name: Setup go
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v5
with:
go-version: '^1.13.1'
- name: Install protoc-gen-go-grpc
run: go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

10 changes: 10 additions & 0 deletions .github/workflows/template/setup-protobuf-template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
name: Setup protobuf
on: [ workflow_call ]

jobs:
- name: Setup protobuf
runs-on: ubuntu-latest
steps:
- uses: arduino/setup-protoc@v3
name: Install Protoc
25 changes: 25 additions & 0 deletions .github/workflows/template/setup-ts-template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
name: Setup TS
on: [ workflow_call ]

jobs:
- name: Setup TS
runs-on: ubuntu-latest
steps:
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
version: 9
run_install: false

- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'

- name: Install dependencies
run: pnpm install

- name: Install protoc-gen-js
run: pnpm install -g protoc-gen-js
26 changes: 26 additions & 0 deletions .github/workflows/test-go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Test go

on:
push:
branches: [ "main" ]
paths:
- go/**
- proto/**
pull_request:
branches: [ "main" ]
paths:
- go/**
- proto/**
workflow_dispatch:

jobs:
- name: Test go
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- uses: ./.github/workflows/template/setup-go-template.yaml

- name: Test
run: make proto test-go
26 changes: 26 additions & 0 deletions .github/workflows/test-ts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Test TS

on:
push:
branches: [ "main" ]
paths:
- ts/**
- proto/**
pull_request:
branches: [ "main" ]
paths:
- ts/**
- proto/**
workflow_dispatch:

jobs:
- name: Test TS
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- uses: ./.github/workflows/template/setup-ts-template.yaml

- name: Test
run: make proto test-ts
16 changes: 11 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
# mode=grpcwebtext prevents client from receiving all the messages at once when using streaming

.PHONY: build proto go ts

build: proto go ts
build: proto build-go build-ts

proto:
protoc --go_out=./go/api --go_opt=paths=source_relative --go-grpc_out=./go/api --go-grpc_opt=paths=source_relative --proto_path ./proto ./proto/api.proto
protoc -I=./proto api.proto \
--js_out=import_style=commonjs,binary:./ts/components/api \
--grpc-web_out=import_style=typescript,mode=grpcwebtext:./ts/components/api

go:
build-go:
cd go && go build .

ts:
build-ts:
cd ts && pnpm build

test: test-ts test-go

test-go:
cd go && go test ./...

test-ts:
cd ts && npm test

clean:
rm -rf ts/out
cd go && go clean
6 changes: 6 additions & 0 deletions go/assets/assets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package assets

import "embed"

//go:embed mock-data
var MockData embed.FS
3 changes: 0 additions & 3 deletions go/internal/config.go

This file was deleted.

104 changes: 104 additions & 0 deletions go/internal/general/general_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package general

import (
"context"
"io"
"net/http"
"strings"
"sync"
"time"

"github.com/moderato-app/live-pprof/api"
"github.com/moderato-app/live-pprof/internal"
)

type GeneralServer struct {
api.UnimplementedGeneralServer
}

func NewGeneralServer() *GeneralServer {
return &GeneralServer{}
}

func (m *GeneralServer) DetectURL(req *api.DetectURLRequest, stream api.General_DetectURLServer) error {
internal.Sugar.Debug("DetectURL req:", req)
wg := sync.WaitGroup{}
mts := [...]internal.MetricsType{
internal.MetricsTypeHeap,
internal.MetricsTypeCPU,
internal.MetricsTypeAllocs,
internal.MetricsTypeGoroutine,
}

urls := make([]string, 0, 5)
urls = append(urls, req.Url)
for _, mt := range mts {
url, err := internal.MetricsURL(req.Url, mt, true)
if err != nil {
internal.Sugar.Error(err)
panic(err)
}
urls = append(urls, url)
}

for _, url := range urls {
wg.Add(1)
go func(url string) {
defer wg.Done()
ctx, cancel := context.WithTimeout(stream.Context(), 3*time.Second)
defer cancel()
result, err := detect(ctx, url)
var errMsg *string
if err != nil {
msg := err.Error()
errMsg = &msg
}
resp := api.DetectURLResponse{
Endpoint: url,
HttpResult: result,
Error: errMsg,
}
err = stream.Send(&resp)
if err != nil {
internal.Sugar.Error(err)
}
}(url)
}
wg.Wait()
return nil
}

func detect(ctx context.Context, url string) (*api.HTTPResult, error) {
client := &http.Client{}
internal.Sugar.Debugf("GET %s", url)

method := http.MethodGet

// don't fetch /profile endpoint since only returns data in pb.gz format
if strings.Contains(url, "/profile?seconds=") {
method = http.MethodHead
}
req, err := http.NewRequestWithContext(ctx, method, url, nil)
if err != nil {
return nil, err
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer func() { _ = resp.Body.Close() }()

data, err := io.ReadAll(resp.Body)

if resp.StatusCode < 200 || resp.StatusCode > 399 {
internal.Sugar.Info("resp.Body: \n" + string(data))
}

bodyStr := string(data)

return &api.HTTPResult{
StatusCode: int32(resp.StatusCode),
StatusText: resp.Status,
Body: &bodyStr,
}, nil
}
53 changes: 9 additions & 44 deletions go/metrics_server.go → go/internal/metrics/metrics_server.go
Original file line number Diff line number Diff line change
@@ -1,57 +1,47 @@
package main
package metrics

import (
"context"
"errors"
"io"
"net/http"
"net/url"
"time"

"github.com/moderato-app/live-pprof/api"
"github.com/moderato-app/live-pprof/internal"
"github.com/moderato-app/pprof/moderato"
)

type MetricsType int

const (
MetricsTypeHeap = MetricsType(iota)
MetricsTypeCPU
MetricsTypeAllocs
MetricsTypeGoroutine
)

type MetricsServer struct {
api.UnimplementedMetricsServer
}

func newMetricsServer() *MetricsServer {
func NewMetricsServer() *MetricsServer {
return &MetricsServer{}
}

func (m *MetricsServer) HeapMetrics(ctx context.Context, req *api.GoMetricsRequest) (*api.GoMetricsResponse, error) {
internal.Sugar.Debug("HeapMetrics req:", req)
return dispatch(ctx, req, MetricsTypeHeap)
return dispatch(ctx, req, internal.MetricsTypeHeap)
}

func (m *MetricsServer) CPUMetrics(ctx context.Context, req *api.GoMetricsRequest) (*api.GoMetricsResponse, error) {
internal.Sugar.Debug("CPUMetrics req:", req)
return dispatch(ctx, req, MetricsTypeCPU)
return dispatch(ctx, req, internal.MetricsTypeCPU)
}

func (m *MetricsServer) AllocsMetrics(ctx context.Context, req *api.GoMetricsRequest) (*api.GoMetricsResponse, error) {
internal.Sugar.Debug("AllocsMetrics req:", req)
return dispatch(ctx, req, MetricsTypeAllocs)
return dispatch(ctx, req, internal.MetricsTypeAllocs)
}

func (m *MetricsServer) GoroutineMetrics(ctx context.Context, req *api.GoMetricsRequest) (*api.GoMetricsResponse, error) {
internal.Sugar.Debug("GoroutineMetrics req:", req)
return dispatch(ctx, req, MetricsTypeGoroutine)
return dispatch(ctx, req, internal.MetricsTypeGoroutine)
}

func dispatch(ctx context.Context, req *api.GoMetricsRequest, mt MetricsType) (*api.GoMetricsResponse, error) {
u, err := prepareUrl(req.Url, mt)
func dispatch(ctx context.Context, req *api.GoMetricsRequest, mt internal.MetricsType) (*api.GoMetricsResponse, error) {
u, err := internal.MetricsURL(req.Url, mt, false)
if err != nil {
internal.Sugar.Error(err)
return nil, err
Expand Down Expand Up @@ -86,7 +76,7 @@ func fetch(ctx context.Context, url string) (*moderato.Metrics, error) {

if resp.StatusCode < 200 || resp.StatusCode > 299 {
internal.Sugar.Error("resp.Body: \n" + string(data))
return nil, errors.New("bad status code: " + resp.Status)
return nil, errors.New("bad status code: " + resp.Status + ". " + string(data))
}

mtr, err := moderato.GetMetricsFromData(data)
Expand All @@ -99,31 +89,6 @@ func fetch(ctx context.Context, url string) (*moderato.Metrics, error) {
return mtr, nil
}

func prepareUrl(baseUrl string, mt MetricsType) (string, error) {
parse, err := url.Parse(baseUrl)
if err != nil {
return "", errors.New("invalid url: " + baseUrl)
}

var u string
if mt == MetricsTypeHeap {
u = parse.JoinPath("heap").String()
} else if mt == MetricsTypeAllocs {
u = parse.JoinPath("allocs").String()
} else if mt == MetricsTypeGoroutine {
u = parse.JoinPath("goroutine").String()
} else if mt == MetricsTypeCPU {
j := parse.JoinPath("profile")
params := url.Values{}
params.Add("seconds", "1")
j.RawQuery = params.Encode()
u = j.String()
} else {
return "", errors.New("invalid fetch type")
}
return u, nil
}

func toResp(mtr *moderato.Metrics) *api.GoMetricsResponse {
var pbItems []*api.Item

Expand Down
Loading

0 comments on commit 46359bf

Please sign in to comment.