Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README_DEV.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ Hatchet provides a number of APIs to output JSON data. They work similarly to th
- reslen
- /api/hatchet/v1.0/hatchets/{hatchet}/logs/all
- /api/hatchet/v1.0/hatchets/{hatchet}/logs/slowops[?topN=] ; The default value of topN is 23.
- /api/hatchet/v1.0/mongodb/{version}/drivers/{driver}[?compatibleWith={driver version}]

## Output Logs in Legacy Format
```bash
Expand Down
93 changes: 58 additions & 35 deletions audit_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,28 @@ func GetAuditTablesTemplate() (*template.Template, error) {
</table>
{{end}}

{{if hasData .Data "failed"}}
{{if hasData .Data "driver"}}
<table style='float: left; margin: 10px 10px;'>
<caption><button class='btn'><i class='fa fa-comment-o'></i></button>Drivers Compatibility</caption>
<tr><th></th><th>Driver</th><th>Version</th><th>IP</th><th>Compatibility</th></tr>
{{$mver := .Info.Version}}
{{range $n, $val := index .Data "driver"}}
<tr><td align=right>{{add $n 1}}</td>
<td>{{index $val.Values 0}}</td><td>{{index $val.Values 1}}</td>
<td>{{$val.Name}}</td>
{{$err := checkDriver $mver $val.Values}}
{{if eq $err nil}}
<td align='center'><i class='fa fa-check'></i></td>
{{else}}
<td><mark>{{$err}}</mark></td>
{{end}}
</tr>
{{end}}
</table>
{{end}}

{{if hasData .Data "failed"}}
<table style='float: left; margin: 10px 10px; clear: left;'>
<caption><button class='btn'
onClick="javascript:location.href='/hatchets/{{.Hatchet}}/logs/all?context=failed'; return false;">
<i class='fa fa-search'></i></button>Failed Operations</caption>
Expand All @@ -64,22 +84,6 @@ func GetAuditTablesTemplate() (*template.Template, error) {
</table>
{{end}}

{{if hasData .Data "op"}}
<table style='float: left; margin: 10px 10px; clear: left;'>
<caption><button class='btn'
onClick="javascript:location.href='/hatchets/{{.Hatchet}}/charts/ops?type=stats'; return false;">
<i class='fa fa-area-chart'></i></button>Operations Stats</caption>
<tr><th></th><th>Operation</th><th>Total</th></tr>
{{range $n, $val := index .Data "op"}}
<tr><td align=right>{{add $n 1}}</td>
<td>
<button class='btn' onClick="javascript:location.href='/hatchets/{{$name}}/charts/ops?type=stats&op={{$val.Name}}'; return false;"><i class='fa fa-area-chart'></i></button>{{$val.Name}}
</td>
<td align=right>{{getFormattedNumber $val.Values 0}}</td></tr>
{{end}}
</table>
{{end}}

{{if hasData .Data "ip"}}
<table style='float: left; margin: 10px 10px;'>
<caption><button class='btn'
Expand All @@ -96,8 +100,24 @@ func GetAuditTablesTemplate() (*template.Template, error) {
</table>
{{end}}

{{if hasData .Data "ns"}}
{{if hasData .Data "op"}}
<table style='float: left; margin: 10px 10px; clear: left;'>
<caption><button class='btn'
onClick="javascript:location.href='/hatchets/{{.Hatchet}}/charts/ops?type=stats'; return false;">
<i class='fa fa-area-chart'></i></button>Operations Stats</caption>
<tr><th></th><th>Operation</th><th>Total</th></tr>
{{range $n, $val := index .Data "op"}}
<tr><td align=right>{{add $n 1}}</td>
<td>
<button class='btn' onClick="javascript:location.href='/hatchets/{{$name}}/charts/ops?type=stats&op={{$val.Name}}'; return false;"><i class='fa fa-area-chart'></i></button>{{$val.Name}}
</td>
<td align=right>{{getFormattedNumber $val.Values 0}}</td></tr>
{{end}}
</table>
{{end}}

{{if hasData .Data "ns"}}
<table style='float: left; margin: 10px 10px;'>
<caption><button class='btn'
onClick="javascript:location.href='/hatchets/{{.Hatchet}}/charts/reslen-ns?ns='; return false;">
<i class='fa fa-pie-chart'></i></button>Stats by Namespaces</caption>
Expand Down Expand Up @@ -153,7 +173,10 @@ func GetAuditTablesTemplate() (*template.Template, error) {
}
return SIMONE_PNG
},
"getFormattedNumber": func(numbers []int, i int) string {
"checkDriver": func(version string, values []interface{}) error {
return CheckDriverCompatibility(version, values[0].(string), values[1].(string))
},
"getFormattedNumber": func(numbers []interface{}, i int) string {
printer := message.NewPrinter(language.English)
return printer.Sprintf("%v", numbers[i])
},
Expand All @@ -165,13 +188,13 @@ func GetAuditTablesTemplate() (*template.Template, error) {
"getDurationFromSeconds": func(s int) string {
return gox.GetDurationFromSeconds(float64(s))
},
"getFormattedDuration": func(numbers []int, i int) string {
return gox.GetDurationFromSeconds(float64(numbers[i]))
"getFormattedDuration": func(numbers []interface{}, i int) string {
return gox.GetDurationFromSeconds(float64(numbers[i].(int)))
},
"getStorageSize": func(s int) string {
return gox.GetStorageSize(float64(s))
},
"getFormattedSize": func(numbers []int, i int) string {
"getFormattedSize": func(numbers []interface{}, i int) string {
return gox.GetStorageSize(numbers[i])
},
"getInfoSummary": func(info HatchetInfo, sage bool) template.HTML {
Expand Down Expand Up @@ -224,7 +247,7 @@ func GetAuditTablesTemplate() (*template.Template, error) {
html += fmt.Sprintf("<span style='color: orange;'>%s</span> version <span style='color: orange;'>%s</span>. ", key, value)
}
}
html += "You should confirm the driver you use is <mark>compatible with the MongoDB server version</mark>. "
html += "See Drivers Compatibility table below for a list of drivers in use. "
} else if len(info.Drivers) > 1 {
html += "It looks like your applications have used a number of drivers, and they are "
cnt := 0
Expand All @@ -238,7 +261,7 @@ func GetAuditTablesTemplate() (*template.Template, error) {
}
}
}
html += "You should double check the drivers you use are <mark>compatible with the MongoDB server version</mark>. "
html += "See Drivers Compatibility table below for a list of drivers in use. "
}
}
return template.HTML(html)
Expand All @@ -258,7 +281,7 @@ func GetAuditTablesTemplate() (*template.Template, error) {
} else if key == "ip" && len(docs) > 0 {
conns := 0
for _, doc := range docs {
conns += doc.Values[0]
conns += doc.Values[0].(int)
}
html += printer.Sprintf("During the time, there were a total of <span style='color: orange;'>%d</span> accepted connections from <span style='color: orange;'>%d</span> ", conns, len(docs))
if len(docs) < 2 {
Expand All @@ -269,18 +292,18 @@ func GetAuditTablesTemplate() (*template.Template, error) {
} else if key == "ns" && len(docs) > 0 {
count := 0
for _, doc := range docs {
count += doc.Values[0]
count += doc.Values[0].(int)
}
html += printer.Sprintf("As many as <span style='color: orange;'>%d</span> different namespaces were accessed a total of <span style='color: orange;'>%d</span> times. ", len(docs), count)
reslen := 0
for _, doc := range docs {
reslen += doc.Values[1]
reslen += doc.Values[1].(int)
}
html += printer.Sprintf("The total response length was around <span style='color: orange;'>%v</span>. ", gox.GetStorageSize(reslen))
} else if key == "stats" && len(docs) > 0 {
for _, doc := range docs {
if doc.Name == "maxConns" {
milli := doc.Values[0]
milli := doc.Values[0].(int)
if milli == 0 {
html += "I didn't find any connections information. "
continue
Expand All @@ -292,25 +315,25 @@ func GetAuditTablesTemplate() (*template.Template, error) {
} else {
html += ". "
}
} else if doc.Name == "maxMilli" && doc.Values[0] > 0 {
milli := doc.Values[0]
} else if doc.Name == "maxMilli" && doc.Values[0].(int) > 0 {
milli := doc.Values[0].(int)
html += "The slowest operation took "
if milli < 1000 {
html += printer.Sprintf("<span style='color: orange;'>%d</span> milliseconds. ", doc.Values[0])
} else {
seconds := float64(milli) / 1000
html += printer.Sprintf("<span style='color: orange;'>%s</span>. ", gox.GetDurationFromSeconds(seconds))
}
} else if doc.Name == "avgMilli" && doc.Values[0] > 0 {
milli := doc.Values[0]
} else if doc.Name == "avgMilli" && doc.Values[0].(int) > 0 {
milli := doc.Values[0].(int)
html += printer.Sprintf("Moreover, the average operation time was <span style='color: orange;'>%d</span> milliseconds", milli)
if milli > 100 {
html += `, where operation time <mark>greater than 100 milliseconds is, IMO, "slow"</mark>. `
} else {
html += ". "
}
} else if doc.Name == "totalMilli" && doc.Values[0] > 0 {
seconds := float64(doc.Values[0]) / 1000
} else if doc.Name == "totalMilli" && doc.Values[0].(int) > 0 {
seconds := float64(doc.Values[0].(int)) / 1000
if seconds < (10 * 60) { // should be calculated with duration
html += printer.Sprintf("The total impact time from slowest operations was %s. ", gox.GetDurationFromSeconds(seconds))
} else if seconds < (60 * 60) {
Expand All @@ -326,7 +349,7 @@ func GetAuditTablesTemplate() (*template.Template, error) {
if doc.Name == "count" {
html += printer.Sprintf(`I found <span style='color: orange;'>%d</span> with <mark><i>COLLSCAN</i> </mark>plan summary. `, doc.Values[0])
} else if doc.Name == "totalMilli" {
seconds := float64(doc.Values[0]) / 1000
seconds := float64(doc.Values[0].(int)) / 1000
html += printer.Sprintf(`The <i>COLLSCAN</i> caused a total of <span style='color: orange;'>%s</span> wasted. `, gox.GetDurationFromSeconds(seconds))
}
}
Expand Down
2 changes: 1 addition & 1 deletion database.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type NameValue struct {

type NameValues struct {
Name string
Values []int
Values []interface{}
}

type Database interface {
Expand Down
43 changes: 43 additions & 0 deletions driver_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2022-present Kuei-chun Chen. All rights reserved.
* driver_handler.go
*/

package hatchet

import (
"encoding/json"
"net/http"

"github.com/julienschmidt/httprouter"
)

// DriverHandler responds to API calls
func DriverHandler(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
/** APIs
* /api/hatchet/v1.0/mongodb/{mongo}/drivers/{driver}?compatibleWith={version}
*/
w.Header().Set("Content-Type", "application/json")
driver := params.ByName("driver")
mongo := params.ByName("mongo")
version := r.URL.Query().Get("compatibleWith")

if version == "" {
versions, err := GetDriverVersions(mongo, driver)
if err != nil {
w.WriteHeader(http.StatusNotFound)
json.NewEncoder(w).Encode(map[string]interface{}{"ok": 0, "error": err.Error()})
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{"ok": 1, "MongoDB": mongo,
"driver": map[string]interface{}{"name": driver, "versions": versions}})
return
} else if err := CheckDriverCompatibility(mongo, driver, version); err != nil {
w.WriteHeader(http.StatusNotFound)
json.NewEncoder(w).Encode(map[string]interface{}{"ok": 0, "error": err.Error()})
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{"ok": 1})
}
113 changes: 113 additions & 0 deletions drivers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright 2022-present Kuei-chun Chen. All rights reserved.
* drivers.go
*/

package hatchet

import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"strings"
)

var driversIns *map[string]interface{}

// GetDrivers returns *map[string]interface{} instance
func GetDrivers() *map[string]interface{} {
if driversIns == nil {
filename := "drivers.json"
data, err := os.ReadFile(filename)
if err != nil {
url := "https://raw.githubusercontent.com/simagix/hatchet/main/drivers.json"
log.Println("download driver manifest from", url)
resp, err := http.Get(url)
if err != nil {
return nil
}
defer resp.Body.Close()

if data, err = io.ReadAll(resp.Body); err != nil {
return nil
}
fname := "drivers.temp"
log.Println("write driver manifest to", fname)
_ = os.WriteFile(fname, data, 0644)
}

// Parse JSON data into a map
var m map[string]interface{}
if err := json.Unmarshal(data, &m); err != nil {
return nil
}
driversIns = &m
}
return driversIns
}

func GetDriverVersions(mongo string, driver string) ([]interface{}, error) {
var versions []interface{}
if mongo == "" {
return versions, fmt.Errorf("missing MongoDB version")
} else if driver == "" {
return versions, fmt.Errorf("missing driver info")
}
mongo = strings.TrimPrefix(mongo, "v")
toks := strings.Split(mongo, ".")
mongo = strings.Join(toks[:2], ".")

drivers := GetDrivers()
if drivers == nil {
return versions, fmt.Errorf("missing driver data")
}
driverData, ok := (*drivers)[mongo].(map[string]interface{})
if !ok {
return versions, fmt.Errorf("missing MongoDB v%v driver data", mongo)
}
versions, ok = driverData[driver].([]interface{})
if !ok || len(versions) < 1 {
return versions, fmt.Errorf(`missing MongoDB v%v driver "%v" data`, mongo, driver)
}
return versions, nil
}

func CheckDriverCompatibility(mongo string, driver string, version string) error {
versions, err := GetDriverVersions(mongo, driver)
if err != nil {
return err
}
if version == "" {
return fmt.Errorf("missing driver info")
}
version = strings.TrimPrefix(version, "v")
toks := strings.Split(version, ".")
version = strings.Join(toks[:2], ".")
if compareVersions(version, versions[0].(string)) < 0 {
return fmt.Errorf("MongoDB v%v requires a minimum driver version of v%v", mongo, versions[0])
}
return nil
}

func compareVersions(v1 string, v2 string) int {
// Split the version strings into their respective integers
v1Split := strings.Split(v1, ".")
a1 := ToInt(v1Split[0])
b1 := ToInt(v1Split[1])

v2Split := strings.Split(v2, ".")
a2 := ToInt(v2Split[0])
b2 := ToInt(v2Split[1])

// Compare the integers
if a1 < a2 || (a1 == a2 && b1 < b2) {
return -1
} else if a1 > a2 || (a1 == a2 && b1 > b2) {
return 1
} else {
return 0
}
}
Loading