公共绘图函数
package charts
import (
"bytes"
"log"
"os"
chart "github.com/wcharczuk/go-chart/v2"
drawing "github.com/wcharczuk/go-chart/v2/drawing"
"golang.org/x/text/message"
)
func DrawChart3AndByteArr(infoArr []string, seriesArr []chart.Series, fileName string) []byte {
ba := DrawChart2ByteArr3(infoArr, seriesArr)
log.Println(fileName)
fo, err := os.Create(fileName)
if err != nil {
panic(err)
}
if _, err := fo.Write(ba); err != nil {
panic(err)
}
return ba
}
func DrawChart2ByteArr3(infoArr []string, seriesArr []chart.Series) []byte {
pp := message.NewPrinter(message.MatchLanguage("en"))
graph := chart.Chart{
Background: chart.Style{
Padding: chart.Box{
Top: 20,
Left: 20,
},
},
Font: GetZWFont(),
XAxis: chart.XAxis{
Name: "时间:分钟",
NameStyle: chart.Style{
FontColor: drawing.Color{R: 255, G: 0, B: 0, A: 255},
},
ValueFormatter: chart.TimeValueFormatterWithFormat("15:04"),
},
YAxis: chart.YAxis{
ValueFormatter: func(v interface{}) string {
if vf, isFloat := v.(float64); isFloat {
return pp.Sprintf("%0.f", vf)
}
return ""
},
},
Series: seriesArr,
}
if len(infoArr) > 0 {
graph.Elements = []chart.Renderable{
TextAndLineInfo(infoArr, &graph),
}
}
buffer := bytes.NewBuffer([]byte{})
err := graph.Render(chart.PNG, buffer)
if err != nil {
log.Fatal(err)
}
return buffer.Bytes()
}
func TextAndLineInfo(txtArr []string, c *chart.Chart) chart.Renderable {
var labels []string
var lines []chart.Style
for index, s := range c.Series {
style := s.GetStyle()
if !style.Hidden {
if _, isAnnotationSeries := s.(chart.AnnotationSeries); !isAnnotationSeries {
labels = append(labels, s.GetName())
lines = append(lines, s.GetStyle().InheritFrom(getStyleDefaultsSeries(c, index)))
}
}
}
return func(r chart.Renderer, cb chart.Box, chartDefaults chart.Style) {
r.SetFont(GetZWFont())
r.SetFontColor(drawing.ColorBlue)
r.SetFontSize(14)
i := 0
for _, txt := range txtArr {
if len(txt) > 0 {
r.Text(txt, 30+i*10, 40+i*30)
i++
}
}
tx := 30 + i*10
for x := 0; x < len(labels); x++ {
label := labels[x]
if len(label) > 0 {
ty := 40 + i*30
r.Text(label, tx, ty)
tb := r.MeasureText(label)
lx := tx + tb.Width() + 5
ly := ty - 6
r.SetStrokeColor(lines[x].GetStrokeColor())
r.SetStrokeWidth(lines[x].GetStrokeWidth())
r.SetStrokeDashArray(lines[x].GetStrokeDashArray())
r.MoveTo(lx, ly)
r.LineTo(lx+30, ly)
r.Stroke()
i++
}
}
}
}
func getStyleDefaultsSeries(c *chart.Chart, seriesIndex int) chart.Style {
return chart.Style{
DotColor: c.GetColorPalette().GetSeriesColor(seriesIndex),
StrokeColor: c.GetColorPalette().GetSeriesColor(seriesIndex),
StrokeWidth: chart.DefaultSeriesLineWidth,
Font: c.GetFont(),
FontSize: chart.DefaultFontSize,
}
}
加载字体
package charts
import (
"io/ioutil"
"log"
"github.com/golang/freetype/truetype"
)
const (
fontFile = "/Library/Fonts/Microsoft-YaHei.ttf"
)
func GetZWFont() *truetype.Font {
fontBytes, err := ioutil.ReadFile(fontFile)
if err != nil {
log.Println(err)
return nil
}
font, err := truetype.Parse(fontBytes)
if err != nil {
log.Println(err)
return nil
}
return font
}
绘图
package report
import (
"fmt"
"log"
"os"
"analysis"
"charts"
"tools"
"time"
"github.com/wcharczuk/go-chart/v2"
"github.com/wcharczuk/go-chart/v2/drawing"
)
func BuildTrendChart(cad *analysis.CacheAppkeyData) {
buildTrendChartTransaction("", cad.Appkey, "qpm", cad.BeginTime, cad.EndTime, cad.TransactionQPMMap, cad.TransactionQPMpTestMap)
log.Println("qpm")
buildTrendChartTransaction("", cad.Appkey, "avg", cad.BeginTime, cad.EndTime, cad.TransactionAvgMap, cad.TransactionAvgpTestMap)
log.Println("avg")
buildTrendChartTransaction("", cad.Appkey, "error", cad.BeginTime, cad.EndTime, cad.TransactionErrorMap, cad.TransactionErrorpTestMap)
log.Println("error")
buildTrendChartThroughput(cad.Appkey, cad.BeginTime, cad.EndTime, cad.TransactionQPMMap, cad.TransactionQPMpTestMap, cad.TransactionAvgMap, cad.TransactionAvgpTestMap)
log.Println("throughput")
buildTrendChartHosts("", cad.Appkey, "cpu.busy", cad.BeginTime, cad.EndTime, cad.HostsCPUBusyMap)
log.Println("cpu.busy")
buildTrendChartHosts("", cad.Appkey, "jvm.fullgc.count", cad.BeginTime, cad.EndTime, cad.HostsJvmFullgcCountMap)
log.Println("jvm.fullgc.count")
buildTrendChartHosts("", cad.Appkey, "jvm.thread.blocked.count", cad.BeginTime, cad.EndTime, cad.HostsJvmThreadBlockedCountMap)
log.Println("jvm.thread.blocked.count")
buildTrendChartHosts("", cad.Appkey, "mem.memused.percent", cad.BeginTime, cad.EndTime, cad.HostsMemMemusedPercentMap)
log.Println("mem.memused.percent")
buildTrendChartHosts("", cad.Appkey, "mem.swapused.percent", cad.BeginTime, cad.EndTime, cad.HostsMemSwapusedPercentMap)
log.Println("mem.swapused.percent")
BuildCapacityScatterPlot1(cad)
}
func buildTrendChartHosts(showtype, octokey, datatype string, begin, end time.Time, tMap map[string]map[time.Time]float64) {
xvArr := [][]time.Time{}
yvArr := [][]float64{}
nameArr := []string{}
hasData := false
for hulkName, dmap := range tMap {
nameArr = append(nameArr, hulkName)
xv := []time.Time{}
yv := []float64{}
for curr := begin; !curr.After(end); curr = curr.Add(time.Minute) {
v1, ex1 := dmap[curr]
if ex1 {
xv = append(xv, curr)
yv = append(yv, v1)
hasData = true
}
}
xvArr = append(xvArr, xv)
yvArr = append(yvArr, yv)
}
if hasData {
centerTime, err := tools.GetCenterTime(begin, end)
if err != nil {
log.Println(err)
}
path := pathCheck(centerTime)
fileName := fmt.Sprintf("%s/%s-%s-%s.png", path, octokey, datatype, centerTime.Format("20060102"))
BuildIMGFile2(showtype, fileName, true, false, []string{fmt.Sprintf("%s-%s", octokey, datatype)}, nameArr, xvArr, yvArr...)
}
}
func buildTrendChartTransaction(showtype, octokey, datatype string, begin, end time.Time, tMap, tpTestMap map[time.Time]float64) {
centerTime, err := tools.GetCenterTime(begin, end)
if err != nil {
log.Println(err)
}
hasData := false
xv1 := []time.Time{}
yv1 := []float64{}
xvpTest := []time.Time{}
yvpTest := []float64{}
xvYesterday := []time.Time{}
yvYesterday := []float64{}
xvWeek := []time.Time{}
yvWeek := []float64{}
xvMonth := []time.Time{}
yvMonth := []float64{}
for curr := begin; !curr.After(end); curr = curr.Add(time.Minute) {
v1, ex1 := tMap[curr]
if ex1 {
xv1 = append(xv1, curr)
yv1 = append(yv1, v1)
hasData = true
} else {
if curr == begin {
xv1 = append(xv1, curr)
yv1 = append(yv1, 0.0)
} else if curr == end {
xv1 = append(xv1, curr)
yv1 = append(yv1, 0.0)
}
}
v2, ex2 := tpTestMap[curr]
if ex2 {
xvpTest = append(xvpTest, curr)
yvpTest = append(yvpTest, v2)
hasData = true
}
v3, ex3 := tMap[curr.AddDate(0, 0, -1)]
if ex3 {
xvYesterday = append(xvYesterday, curr)
yvYesterday = append(yvYesterday, v3)
hasData = true
}
v4, ex4 := tMap[curr.AddDate(0, 0, -7)]
if ex4 {
xvWeek = append(xvWeek, curr)
yvWeek = append(yvWeek, v4)
hasData = true
}
v5, ex5 := tMap[curr.AddDate(0, -1, 0)]
if ex5 {
xvMonth = append(xvMonth, curr)
yvMonth = append(yvMonth, v5)
hasData = true
}
}
nameArr := []string{}
xvArr := [][]time.Time{}
yvArr := [][]float64{}
if len(xv1) > 0 {
nameArr = append(nameArr, fmt.Sprintf("昨日%s", centerTime.Format("20060102")))
xvArr = append(xvArr, xv1)
yvArr = append(yvArr, yv1)
}
if len(xvpTest) > 0 {
nameArr = append(nameArr, fmt.Sprintf("昨日压测%s", centerTime.Format("20060102")))
xvArr = append(xvArr, xvpTest)
yvArr = append(yvArr, yvpTest)
}
if len(xvYesterday) > 0 {
nameArr = append(nameArr, fmt.Sprintf("前日%s", centerTime.AddDate(0, 0, -1).Format("20060102")))
xvArr = append(xvArr, xvYesterday)
yvArr = append(yvArr, yvYesterday)
}
if len(xvWeek) > 0 {
nameArr = append(nameArr, fmt.Sprintf("上周同期%s", centerTime.AddDate(0, 0, -7).Format("20060102")))
xvArr = append(xvArr, xvWeek)
yvArr = append(yvArr, yvWeek)
}
if len(xvMonth) > 0 {
nameArr = append(nameArr, fmt.Sprintf("上月同期%s", centerTime.AddDate(0, -1, 0).Format("20060102")))
xvArr = append(xvArr, xvMonth)
yvArr = append(yvArr, yvMonth)
}
if hasData {
path := pathCheck(centerTime)
fileName := fmt.Sprintf("%s/%s-%s-%s.png", path, octokey, datatype, centerTime.Format("20060102"))
BuildIMGFile2(showtype, fileName, true, true, []string{fmt.Sprintf("%s-%s", octokey, datatype)}, nameArr, xvArr, yvArr...)
}
}
func buildTrendChartThroughput(octokey string, begin, end time.Time, qpmMap, qpmpTestMap, avgMap, avgpTestMap map[time.Time]float64) {
centerTime, err := tools.GetCenterTime(begin, end)
if err != nil {
log.Println(err)
}
hasData := false
xv1 := []time.Time{}
yv1 := []float64{}
xvYesterday := []time.Time{}
yvYesterday := []float64{}
xvWeek := []time.Time{}
yvWeek := []float64{}
xvMonth := []time.Time{}
yvMonth := []float64{}
for curr := begin; !curr.After(end); curr = curr.Add(time.Minute) {
v1, ex1 := qpmMap[curr]
vt1, ext1 := avgMap[curr]
if ex1 && ext1 {
xv1 = append(xv1, curr)
vp1, exp1 := qpmpTestMap[curr]
vtp1, extp1 := avgpTestMap[curr]
if exp1 && extp1 {
yv1 = append(yv1, v1*vt1+vp1*vtp1)
} else {
yv1 = append(yv1, v1*vt1)
}
hasData = true
}
v3, ex3 := qpmMap[curr.AddDate(0, 0, -1)]
vt3, ext3 := avgMap[curr.AddDate(0, 0, -1)]
if ex3 && ext3 {
xvYesterday = append(xvYesterday, curr)
yvYesterday = append(yvYesterday, v3*vt3)
hasData = true
}
v4, ex4 := qpmMap[curr.AddDate(0, 0, -7)]
vt4, ext4 := avgMap[curr.AddDate(0, 0, -7)]
if ex4 && ext4 {
xvWeek = append(xvWeek, curr)
yvWeek = append(yvWeek, v4*vt4)
hasData = true
}
v5, ex5 := qpmMap[curr.AddDate(0, -1, 0)]
vt5, ext5 := avgMap[curr.AddDate(0, -1, 0)]
if ex5 && ext5 {
xvMonth = append(xvMonth, curr)
yvMonth = append(yvMonth, v5*vt5)
hasData = true
}
}
nameArr := []string{}
xvArr := [][]time.Time{}
yvArr := [][]float64{}
if len(xv1) > 0 {
nameArr = append(nameArr, fmt.Sprintf("昨日%s吞吐量", centerTime.Format("20060102")))
xvArr = append(xvArr, xv1)
yvArr = append(yvArr, yv1)
}
if len(xvYesterday) > 0 {
nameArr = append(nameArr, fmt.Sprintf("前日%s吞吐量", centerTime.AddDate(0, 0, -1).Format("20060102")))
xvArr = append(xvArr, xvYesterday)
yvArr = append(yvArr, yvYesterday)
}
if len(xvWeek) > 0 {
nameArr = append(nameArr, fmt.Sprintf("上周同期%s吞吐量", centerTime.AddDate(0, 0, -7).Format("20060102")))
xvArr = append(xvArr, xvWeek)
yvArr = append(yvArr, yvWeek)
}
if len(xvMonth) > 0 {
nameArr = append(nameArr, fmt.Sprintf("上月同期%s吞吐量", centerTime.AddDate(0, -1, 0).Format("20060102")))
xvArr = append(xvArr, xvMonth)
yvArr = append(yvArr, yvMonth)
}
if hasData {
path := pathCheck(centerTime)
fileName := fmt.Sprintf("%s/%s-%s-%s.png", path, octokey, "throughput", centerTime.Format("20060102"))
BuildIMGFile2("dot", fileName, true, true, []string{fmt.Sprintf("%s-%s", octokey, "throughput")}, nameArr, xvArr, yvArr...)
}
}
func BuildIMGFile2(showtype, filename string, enforce, showName bool, lbInfoArr, nameArr []string, xv [][]time.Time, yv ...[]float64) {
_, err1 := os.ReadFile(filename)
if !enforce && err1 == nil {
log.Printf("不需要修改走势图%s\r", filename)
log.Println(err1)
return
}
sArr := []chart.Series{}
for i, name := range nameArr {
if showtype == "dot" {
if showName {
sArr = append(sArr, chart.TimeSeries{
Name: name,
Style: chart.Style{
StrokeColor: getColor(i),
StrokeWidth: chart.Disabled,
DotWidth: 1,
},
XValues: xv[i],
YValues: yv[i],
})
} else {
sArr = append(sArr, chart.TimeSeries{
Style: chart.Style{
StrokeColor: getColor(i),
StrokeWidth: chart.Disabled,
DotWidth: 1,
},
XValues: xv[i],
YValues: yv[i],
})
}
} else {
if showName {
sArr = append(sArr, chart.TimeSeries{
Name: name,
Style: chart.Style{
StrokeColor: getColor(i),
},
XValues: xv[i],
YValues: yv[i],
})
} else {
sArr = append(sArr, chart.TimeSeries{
Style: chart.Style{
StrokeColor: getColor(i),
},
XValues: xv[i],
YValues: yv[i],
})
}
}
}
if len(nameArr) > 0 {
log.Printf("开始绘图 %s\r\n", filename)
charts.DrawChart3AndByteArr(lbInfoArr, sArr, filename)
}
}
func pathCheck(ct time.Time) string {
tools.PathCreate(fmt.Sprintf("./data/%s", ct.Format("200601")))
tools.PathCreate(fmt.Sprintf("./data/%s/imgs", ct.Format("200601")))
tools.PathCreate(fmt.Sprintf("./data/%s/imgs/%s", ct.Format("200601"), ct.Format("20060102")))
tools.PathCreate(fmt.Sprintf("./data/%s/imgs/%s/day", ct.Format("200601"), ct.Format("20060102")))
return fmt.Sprintf("./data/%s/imgs/%s/day", ct.Format("200601"), ct.Format("20060102"))
}
func getColor(i int) drawing.Color {
switch i % 5 {
case 0:
return drawing.Color{R: 0, G: 0, B: 255, A: 255}
case 1:
return drawing.Color{R: 0, G: 255, B: 0, A: 255}
case 2:
return drawing.Color{R: 148, G: 0, B: 211, A: 255}
case 3:
return drawing.Color{R: 220, G: 20, B: 60, A: 255}
case 4:
return drawing.Color{R: 124, G: 205, B: 124, A: 255}
default:
return drawing.Color{R: 220, G: 160, B: 122, A: 255}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
2014-11-07 Golang 的 TOML库