├── cmd ├── measuregen │ ├── README.md │ ├── go.mod │ ├── go.sum │ └── measuregen.go └── sqlstr │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── sqlstr.go │ └── testdata │ └── main.go ├── error.go ├── go.mod ├── go.sum ├── handler.go ├── httpclient.go └── init.go /cmd/measuregen/README.md: -------------------------------------------------------------------------------- 1 | # measuregen 2 | 3 | 標準入力にGoのソースコードを与えると 4 | [measure](https://github.com/najeira/measure)の計測用の関数呼び出しを追加します。 5 | 6 | ```sh 7 | $ cat main.go | measuregen 8 | ``` 9 | 10 | なお、同じソースに何度書けても同じ関数には2度は同じ関数呼び出しを追加しないようになっています。 11 | そのため、新しく追加した関数に対して計測用の関数呼び出しを差し込みたい場合は単にもう一度`measuregen`をかければ良いです。 12 | 13 | ```sh 14 | # 結果は同じ 15 | $ cat main.go | measuregen | measuregen 16 | ``` 17 | -------------------------------------------------------------------------------- /cmd/measuregen/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/isucontools/measuregen 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/najeira/measure v0.0.0-20181003032124-22a5dc927668 7 | github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 // indirect 8 | golang.org/x/tools v0.0.0-20190908135931-fef9eaa9e42b 9 | ) 10 | -------------------------------------------------------------------------------- /cmd/measuregen/go.sum: -------------------------------------------------------------------------------- 1 | github.com/najeira/measure v0.0.0-20181003032124-22a5dc927668 h1:AEKsHFjbPjJJqYSZHbvk+LjK3mtsa6rIhf3zdY4MiiA= 2 | github.com/najeira/measure v0.0.0-20181003032124-22a5dc927668/go.mod h1:yXxWo2ulJTlt8qbsPwmxKbieyeakYs82H1Krl8LQ/8Q= 3 | github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 h1:dY6ETXrvDG7Sa4vE8ZQG4yqWg6UnOcbqTAahkV813vQ= 4 | github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 5 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 6 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 7 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 8 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 9 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 10 | golang.org/x/tools v0.0.0-20190908135931-fef9eaa9e42b h1:s3JPx1wx+Kydg1rKI8uzmNMeYA+rr9m5kiLGVqEBufM= 11 | golang.org/x/tools v0.0.0-20190908135931-fef9eaa9e42b/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 12 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 13 | -------------------------------------------------------------------------------- /cmd/measuregen/measuregen.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "go/ast" 7 | "go/format" 8 | "go/parser" 9 | "go/printer" 10 | "go/token" 11 | "log" 12 | "os" 13 | "strconv" 14 | 15 | "golang.org/x/tools/go/ast/inspector" 16 | ) 17 | 18 | const ( 19 | importPATH = "github.com/najeira/measure" 20 | ) 21 | 22 | func main() { 23 | fset := token.NewFileSet() 24 | f, err := parser.ParseFile(fset, "main.go", os.Stdin, parser.ParseComments) 25 | if err != nil { 26 | log.Fatal("Error:", err) 27 | } 28 | 29 | closures := map[*ast.FuncDecl]int{} 30 | filter := []ast.Node{ 31 | new(ast.FuncDecl), 32 | new(ast.FuncLit), 33 | } 34 | inspector.New([]*ast.File{f}).WithStack(filter, func(n ast.Node, push bool, stack []ast.Node) bool { 35 | if !push { 36 | return true 37 | } 38 | 39 | switch n := n.(type) { 40 | case *ast.FuncDecl: 41 | expr, err := parser.ParseExpr(fmt.Sprintf(`measure.Start("%s").Stop()`, n.Name.Name)) 42 | if err != nil { 43 | log.Fatal("Error:", err) 44 | } 45 | deferStmt := &ast.DeferStmt{Call: expr.(*ast.CallExpr)} 46 | 47 | if hasMeasure(n.Body.List, deferStmt) { 48 | return true 49 | } 50 | 51 | n.Body.List = append([]ast.Stmt{deferStmt}, n.Body.List...) 52 | case *ast.FuncLit: 53 | name := "NONAME" 54 | if parent := findParent(stack); parent != nil { 55 | closures[parent]++ 56 | name = fmt.Sprintf("%s-%d", parent.Name.Name, closures[parent]) 57 | } 58 | expr, err := parser.ParseExpr(fmt.Sprintf(`measure.Start("%s").Stop()`, name)) 59 | if err != nil { 60 | log.Fatal("Error:", err) 61 | } 62 | 63 | deferStmt := &ast.DeferStmt{Call: expr.(*ast.CallExpr)} 64 | 65 | if hasMeasure(n.Body.List, deferStmt) { 66 | return true 67 | } 68 | 69 | n.Body.List = append([]ast.Stmt{deferStmt}, n.Body.List...) 70 | } 71 | 72 | return true 73 | }) 74 | 75 | addImport(f) 76 | 77 | format.Node(os.Stdout, fset, f) 78 | } 79 | 80 | func findParent(stack []ast.Node) *ast.FuncDecl { 81 | for i := range stack { 82 | if funcdecl, ok := stack[i].(*ast.FuncDecl); ok { 83 | return funcdecl 84 | } 85 | } 86 | return nil 87 | } 88 | 89 | func addImport(f *ast.File) { 90 | 91 | for _, im := range f.Imports { 92 | path, err := strconv.Unquote(im.Path.Value) 93 | if err != nil { 94 | continue 95 | } 96 | 97 | // already imported 98 | if path == importPATH { 99 | return 100 | } 101 | } 102 | 103 | importSpec := &ast.ImportSpec{ 104 | Name: ast.NewIdent("measure"), 105 | Path: &ast.BasicLit{ 106 | Kind: token.STRING, 107 | Value: strconv.Quote(importPATH), 108 | }, 109 | } 110 | f.Imports = append(f.Imports, importSpec) 111 | 112 | // Find last imnport group 113 | var lastGenDecl *ast.GenDecl 114 | for _, decl := range f.Decls { 115 | genDecl, ok := decl.(*ast.GenDecl) 116 | if !ok || genDecl.Tok != token.IMPORT { 117 | continue 118 | } 119 | lastGenDecl = genDecl 120 | } 121 | 122 | // The file already have import statements 123 | if lastGenDecl != nil { 124 | lastGenDecl.Specs = append(lastGenDecl.Specs, importSpec) 125 | return 126 | } 127 | 128 | f.Decls = append([]ast.Decl{&ast.GenDecl{ 129 | Tok: token.IMPORT, 130 | Specs: []ast.Spec{importSpec}, 131 | }}, f.Decls...) 132 | } 133 | 134 | func nodeStr(node ast.Node) (string, error) { 135 | fset := token.NewFileSet() 136 | var buf bytes.Buffer 137 | if err := printer.Fprint(&buf, fset, node); err != nil { 138 | return "", err 139 | } 140 | return buf.String(), nil 141 | } 142 | 143 | func hasMeasure(stmts []ast.Stmt, deferStmt *ast.DeferStmt) bool { 144 | if len(stmts) == 0 { 145 | return false 146 | } 147 | 148 | firstStmtStr, err := nodeStr(stmts[0]) 149 | if err != nil { 150 | return false 151 | } 152 | 153 | deferStmtStr, err := nodeStr(deferStmt) 154 | if err != nil { 155 | return false 156 | } 157 | 158 | return firstStmtStr == deferStmtStr 159 | } 160 | -------------------------------------------------------------------------------- /cmd/sqlstr/README.md: -------------------------------------------------------------------------------- 1 | # sqlstr 2 | 3 | 標準入力で与えたソースコード中の文字リテラルのうちSQLっぽいものを抜き出します。 4 | `+`演算子による文字リテラルの結合は結合した状態で抜き出します。 5 | 6 | ```sh 7 | $ cat main.go | sqlstr 8 | ``` 9 | 10 | なお、変数との結合や`fmt.Sprintf`による結合はうまく抜き出せない可能性があります。 11 | -------------------------------------------------------------------------------- /cmd/sqlstr/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/isucontools/sqlstr 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go v1.24.2 7 | golang.org/x/tools v0.0.0-20190920023704-c426260dee6e 8 | ) 9 | -------------------------------------------------------------------------------- /cmd/sqlstr/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aws/aws-sdk-go v1.24.2 h1:UeC7n3+aYUMgH00diplWj8e7rINQ72mIhHMhph3qZcU= 2 | github.com/aws/aws-sdk-go v1.24.2/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 3 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= 4 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 5 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 6 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 7 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 8 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 9 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 10 | golang.org/x/tools v0.0.0-20190920023704-c426260dee6e h1:Z1PYhQToxRHDKT982zpv5mvmVq80qDLv4JtzM6uM8w8= 11 | golang.org/x/tools v0.0.0-20190920023704-c426260dee6e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 12 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 13 | -------------------------------------------------------------------------------- /cmd/sqlstr/sqlstr.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "go/ast" 7 | "go/constant" 8 | "go/parser" 9 | "go/printer" 10 | "go/token" 11 | "go/types" 12 | "log" 13 | "os" 14 | "strconv" 15 | "strings" 16 | 17 | "golang.org/x/tools/go/ast/inspector" 18 | ) 19 | 20 | func main() { 21 | 22 | fset := token.NewFileSet() 23 | 24 | name := "stdin.go" 25 | var src interface{} = os.Stdin 26 | if len(os.Args) >= 2 { 27 | name = os.Args[1] 28 | src = nil 29 | } 30 | 31 | f, err := parser.ParseFile(fset, name, src, parser.ParseComments) 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | 36 | run(fset, f) 37 | } 38 | 39 | func run(fset *token.FileSet, f *ast.File) (rerr error) { 40 | inspector.New([]*ast.File{f}).WithStack(nil, func(n ast.Node, push bool, stack []ast.Node) bool { 41 | 42 | if !push { 43 | return true 44 | } 45 | 46 | expr, ok := n.(ast.Expr) 47 | if !ok { 48 | return true 49 | } 50 | 51 | tv, err := eval(expr) 52 | if err != nil || tv.Value == nil || tv.Value.Kind() != constant.String { 53 | return true 54 | } 55 | s, err := strconv.Unquote(tv.Value.ExactString()) 56 | if err != nil { 57 | rerr = err 58 | return false 59 | } 60 | 61 | // SQL文っぽい 62 | us := strings.ToUpper(strings.TrimSpace(s)) 63 | if strings.HasPrefix(us, "SELECT") || 64 | strings.HasPrefix(us, "INSERT") || 65 | strings.HasPrefix(us, "UPDATE") || 66 | strings.HasPrefix(us, "DELETE") { 67 | 68 | funcDecl := findFunc(stack) 69 | if funcDecl != nil { 70 | fmt.Printf("%s in %s\n", fset.Position(expr.Pos()), funcDecl.Name.Name) 71 | } else { 72 | fmt.Println(fset.Position(expr.Pos())) 73 | } 74 | fmt.Printf("\t%s\n\n", s) 75 | } 76 | 77 | return true 78 | }) 79 | 80 | return 81 | } 82 | 83 | func eval(expr ast.Expr) (types.TypeAndValue, error) { 84 | fset := token.NewFileSet() 85 | var buf bytes.Buffer 86 | if err := printer.Fprint(&buf, fset, expr); err != nil { 87 | return types.TypeAndValue{}, err 88 | } 89 | pkg := types.NewPackage("main", "main") 90 | return types.Eval(fset, pkg, token.NoPos, buf.String()) 91 | } 92 | 93 | func findFunc(stack []ast.Node) *ast.FuncDecl { 94 | for i := range stack { 95 | if funcdecl, ok := stack[i].(*ast.FuncDecl); ok { 96 | return funcdecl 97 | } 98 | } 99 | return nil 100 | } 101 | -------------------------------------------------------------------------------- /cmd/sqlstr/testdata/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | const sql1 = "SELECT * FROM items" 6 | 7 | func main() { 8 | const sql2 = "SELECT * FROM items" 9 | fmt.Println(sql1, sql2) 10 | } 11 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package isucontools 2 | 3 | import ( 4 | "log" 5 | ) 6 | 7 | func Must(err error) { 8 | if err != nil { 9 | log.Panic(err) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tenntenn/isucontools 2 | 3 | go 1.13 4 | 5 | require github.com/hashicorp/go-retryablehttp v0.6.2 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= 3 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 4 | github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= 5 | github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= 6 | github.com/hashicorp/go-retryablehttp v0.6.2 h1:bHM2aVXwBtBJWxHtkSrWuI4umABCUczs52eiUS9nSiw= 7 | github.com/hashicorp/go-retryablehttp v0.6.2/go.mod h1:gEx6HMUGxYYhJScX7W1Il64m6cc2C1mDaW3NQ9sY1FY= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 10 | -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | package isucontools 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | "path/filepath" 8 | ) 9 | 10 | func InitStaticFiles(callback func(urlpath string, handler http.Handler), prefix string) { 11 | wf := func(path string, info os.FileInfo, err error) error { 12 | log.Println(path, info, err) 13 | if path == prefix { 14 | return nil 15 | } 16 | if info.IsDir() { 17 | return nil 18 | } 19 | urlpath := path[len(prefix):] 20 | if urlpath[0] != '/' { 21 | urlpath = "/" + urlpath 22 | } 23 | log.Println("Registering", urlpath, path) 24 | f, err := os.Open(path) 25 | if err != nil { 26 | log.Println(err) 27 | return nil 28 | } 29 | content := make([]byte, info.Size()) 30 | f.Read(content) 31 | f.Close() 32 | 33 | handler := func(w http.ResponseWriter, r *http.Request) { 34 | if path[len(path)-4:] == ".css" { 35 | w.Header().Set("Content-Type", "text/css") 36 | } else if path[len(path)-3:] == ".js" { 37 | w.Header().Set("Content-Type", "application/javascript") 38 | } 39 | w.Write(content) 40 | } 41 | callback(urlpath, http.HandlerFunc(handler)) 42 | return nil 43 | } 44 | filepath.Walk(prefix, wf) 45 | } 46 | -------------------------------------------------------------------------------- /httpclient.go: -------------------------------------------------------------------------------- 1 | package isucontools 2 | 3 | import ( 4 | "net/http" 5 | 6 | retryablehttp "github.com/hashicorp/go-retryablehttp" 7 | ) 8 | 9 | var HTTPClient = retryablehttp.NewClient() 10 | 11 | func initHttpClient() { 12 | // https://qiita.com/ono_matope/items/60e96c01b43c64ed1d18 13 | http.DefaultTransport.(*http.Transport).MaxIdleConnsPerHost = 1000 14 | } 15 | -------------------------------------------------------------------------------- /init.go: -------------------------------------------------------------------------------- 1 | package isucontools 2 | 3 | func Init() { 4 | initHttpClient() 5 | } 6 | --------------------------------------------------------------------------------