line-chart/LineChart.go

353 lines
7.8 KiB
Go

package main
import (
"database/sql"
"flag"
"fmt"
"log"
"net/http"
"os"
"strings"
"text/template"
"time"
"github.com/go-echarts/go-echarts/v2/charts"
"github.com/go-echarts/go-echarts/v2/opts"
_ "github.com/go-sql-driver/mysql"
ini "gopkg.in/ini.v1"
)
var verb bool = false
var file string = ""
var cfg *ini.File
type DataSet struct {
Alias string
Item string
Result string
UpdateTime string
}
func main() {
flag.BoolVar(&verb, "v", false, "Show Verbose Debug Messages")
flag.StringVar(&file, "f", "", "Config File Path")
flag.Parse()
log.SetOutput(os.Stdout)
log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds)
if file == "" {
file = strings.TrimSuffix(os.Args[0], ".exe") + ".ini"
}
var err error
cfg, err = ini.Load(file)
if err != nil {
log.Printf("[ERR] %v\r\n", err)
os.Exit(1)
}
addr := cfg.Section("HTTP").Key("ListenIP").String()
port := cfg.Section("HTTP").Key("ListenPort").String()
http.HandleFunc("/", handleIndex)
http.HandleFunc("/echart/", eChart)
http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("./assets"))))
http.ListenAndServe(addr+":"+port, nil)
}
func handleIndex(w http.ResponseWriter, r *http.Request) {
if r.RequestURI == "/favicon.ico" {
return
}
log.Printf("[MSG] Received Request: %s, %s\r\n", r.RemoteAddr, r.RequestURI)
defer r.Body.Close()
type Data struct {
Title string
Host string
}
data := Data{
Title: cfg.Section("HTTP").Key("Title").String(),
Host: cfg.Section("HTTP").Key("Host").String(),
}
tpl, err := template.ParseFiles("root.html")
if err != nil {
log.Printf("[ERR] %v\r\n", err)
return
}
err = tpl.Execute(w, data)
if err != nil {
log.Printf("[ERR] %v\r\n", err)
return
}
}
func eChart(w http.ResponseWriter, r *http.Request) {
if r.RequestURI == "/favicon.ico" {
return
}
fmt.Println()
log.Printf("[MSG] Received Request: %s, %s\r\n", r.RemoteAddr, r.RequestURI)
defer r.Body.Close()
if err := r.ParseForm(); err != nil {
log.Printf("[ERR] %v\r\n", err)
}
type Params struct {
Range string ""
Alias string ""
Class string ""
Scope string ""
}
var params Params
params.Range = r.Form.Get("range")
params.Scope = r.Form.Get("scope")
params.Alias = r.Form.Get("alias")
params.Class = r.Form.Get("class")
log.Printf("[MSG] Request Params: %#v\r\n", params)
timeAxis := make([]string, 0)
endTime := time.Now()
beginTime := endTime.AddDate(0, 0, -30)
bt := beginTime
for i := 1; i <= 60*24*30; i++ {
bt = bt.Add(time.Minute)
timeAxis = append(timeAxis, bt.Format("01-02 15:04"))
}
// Parsing Config - Database
cfg.Reload()
dbHost := cfg.Section("Database").Key("Host").MustString("127.0.0.1")
dbPort := cfg.Section("Database").Key("Port").MustString("3306")
dbSchema := cfg.Section("Database").Key("Database").MustString("monitoring")
dbTable := cfg.Section("Database").Key("Table").MustString("server")
dbColumns := cfg.Section("Database").Key("Columns").MustString("alias,item,result,update_time")
dbUsername := cfg.Section("Database").Key("Username").String()
dbPassword := cfg.Section("Database").Key("Password").String()
if dbUsername == "" || dbPassword == "" {
log.Println("[ERR] Invalid Database Configuration.")
return
}
qry := fmt.Sprintf(
"SELECT %s FROM %s WHERE alias='%s' AND item='%s' AND update_stmp>=%d ORDER BY seqid ASC;",
dbColumns,
dbTable,
params.Alias,
params.Class,
beginTime.Unix(),
)
log.Printf("[MSG] Query SQL: \r\n%s\r\n", qry)
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", dbUsername, dbPassword, dbHost, dbPort, dbSchema)
conn, err := sql.Open("mysql", dsn)
if err != nil {
log.Printf("[ERR] %v\r\n", err)
return
}
defer conn.Close()
if err := conn.Ping(); err != nil {
log.Printf("[ERR] %v\r\n", err)
return
}
defer conn.Close()
rows, err := conn.Query(qry)
if err != nil {
log.Printf("[ERR] %v\r\n", err)
return
}
defer rows.Close()
log.Println("[MSG] Query Successed.")
baseData := make(map[string]string, 0)
for rows.Next() {
var data DataSet
err := rows.Scan(&data.Alias, &data.Item, &data.Result, &data.UpdateTime)
if err != nil {
log.Printf("[ERR] %v\r\n", err)
return
}
baseData[data.UpdateTime] = data.Result
}
log.Printf("[MSG] Total Scanned: %d\r\n", len(baseData))
var dots []opts.LineData = make([]opts.LineData, 0)
for _, t := range timeAxis {
tmp, ok := baseData[t]
if ok {
dots = append(dots, opts.LineData{Value: tmp})
} else {
dots = append(dots, opts.LineData{Value: "null"})
}
}
log.Println("[MSG] Finished Sort Metadata.")
// Parsing Config - Chart Titles
chartTitle := cfg.Section("Chart").Key("Title").String()
chartSubtitle := cfg.Section("Chart").Key("Subtitle").String()
chartSeries := cfg.Section("Chart").Key("Series").String()
assetsURL := cfg.Section("Chart").Key("AssetsURL").String()
var xPercent float32 = 0
if params.Range == "all" {
xPercent = 0
}
if params.Range == "7days" {
xPercent = 76.66
}
if params.Range == "3days" {
xPercent = 90
}
if params.Range == "2days" {
xPercent = 93.33
}
if params.Range == "1day" {
xPercent = 96.66
}
if params.Range == "12hrs" {
xPercent = 98.33
}
if params.Range == "6hrs" {
xPercent = 99.16
}
if params.Alias != "" {
chartSubtitle = params.Alias
}
if params.Class != "" {
chartTitle = params.Class
}
chart := charts.NewLine()
// lang := make([]string, 0)
// lang = append(lang, "data view")
// lang = append(lang, "turn off")
// lang = append(lang, "refresh")
chart.SetGlobalOptions(
charts.WithTitleOpts(
opts.Title{
Left: "center",
Top: "0",
Title: chartTitle,
TitleStyle: &opts.TextStyle{
Color: "#000000",
FontSize: 18,
FontFamily: "Calibri",
},
Subtitle: chartSubtitle,
SubtitleStyle: &opts.TextStyle{
Color: "#222222",
FontSize: 16,
FontFamily: "Calibri",
},
},
),
charts.WithInitializationOpts(
opts.Initialization{
PageTitle: "eChart",
BackgroundColor: "#FFFFFF",
Width: "800px",
Height: "600px",
AssetsHost: assetsURL,
},
),
charts.WithDataZoomOpts(
opts.DataZoom{
Type: "inside",
Start: xPercent,
End: 100,
},
),
charts.WithXAxisOpts(
opts.XAxis{
Min: "dataMin",
Max: "dataMax",
AxisLabel: &opts.AxisLabel{
Show: true,
ShowMinLabel: true,
ShowMaxLabel: true,
Rotate: 45,
FontSize: "14",
FontWeight: "600",
FontFamily: "Calibri",
LineHeight: "250",
},
},
),
charts.WithTooltipOpts(
opts.Tooltip{
Show: true,
Trigger: "axis",
},
),
charts.WithToolboxOpts(
opts.Toolbox{
Show: true,
Feature: &opts.ToolBoxFeature{
SaveAsImage: &opts.ToolBoxFeatureSaveAsImage{
Show: true,
Type: "png",
},
DataView: &opts.ToolBoxFeatureDataView{
Show: true,
},
Restore: &opts.ToolBoxFeatureRestore{
Show: true,
},
},
},
),
charts.WithLegendOpts(
opts.Legend{
Show: false,
},
),
)
chart.SetXAxis(timeAxis).AddSeries(chartSeries, dots).
SetSeriesOptions(
charts.WithLineChartOpts(
opts.LineChart{
ConnectNulls: true,
},
),
charts.WithMarkPointNameTypeItemOpts(
opts.MarkPointNameTypeItem{
Name: "Max",
Type: "max",
},
opts.MarkPointNameTypeItem{
Name: "Min",
Type: "min",
},
),
charts.WithMarkPointStyleOpts(
opts.MarkPointStyle{
SymbolSize: 50,
Label: &opts.Label{
Show: true,
Position: "inside",
},
},
),
charts.WithMarkLineNameTypeItemOpts(
opts.MarkLineNameTypeItem{
Name: "Avrg",
Type: "average",
},
),
// charts.WithLabelOpts(
// opts.Label{
// Show: true,
// Position: "top",
// },
// ),
)
chart.Render(w)
log.Println("[MSG] Finished Render Web Page.")
}