From fbe82c308268efd4918ef8a441c35b3c660ed174 Mon Sep 17 00:00:00 2001 From: yusing Date: Thu, 4 Sep 2025 06:25:07 +0800 Subject: [PATCH] refactor(metrics): optimize JSON marshaling in SystemInfo and Aggregated structures for improved performance and memory management --- internal/metrics/systeminfo/json.go | 58 +++++++------------ internal/metrics/systeminfo/system_info.go | 67 ++++++++++++---------- 2 files changed, 57 insertions(+), 68 deletions(-) diff --git a/internal/metrics/systeminfo/json.go b/internal/metrics/systeminfo/json.go index 9b640677..83cfe71a 100644 --- a/internal/metrics/systeminfo/json.go +++ b/internal/metrics/systeminfo/json.go @@ -1,20 +1,13 @@ package systeminfo import ( - "encoding/json" "fmt" "strconv" - - "github.com/shirou/gopsutil/v4/sensors" - "github.com/yusing/go-proxy/internal/utils/synk" ) -var bufPool = synk.GetBytesPool() - // explicitly implement MarshalJSON to avoid reflection. func (s *SystemInfo) MarshalJSON() ([]byte, error) { - b := bufPool.Get() - defer bufPool.Put(b) + b := make([]byte, 0, 4096) b = append(b, '{') @@ -114,15 +107,14 @@ func (s *SystemInfo) MarshalJSON() ([]byte, error) { // sensors b = append(b, `,"sensors":`...) if len(s.Sensors) > 0 { - b = append(b, '{') + b = append(b, '[') first := true for _, sensor := range s.Sensors { if !first { b = append(b, ',') } b = fmt.Appendf(b, - `"%s":{"name":"%s","temperature":%.2f,"high":%.2f,"critical":%.2f}`, - sensor.SensorKey, + `{"name":"%s","temperature":%.2f,"high":%.2f,"critical":%.2f}`, sensor.SensorKey, sensor.Temperature, sensor.High, @@ -130,7 +122,7 @@ func (s *SystemInfo) MarshalJSON() ([]byte, error) { ) first = false } - b = append(b, '}') + b = append(b, ']') } else { b = append(b, "null"...) } @@ -139,34 +131,22 @@ func (s *SystemInfo) MarshalJSON() ([]byte, error) { return b, nil } -func (s *Sensors) UnmarshalJSON(data []byte) error { - var v map[string]map[string]any - if err := json.Unmarshal(data, &v); err != nil { - return err - } - if len(v) == 0 { - return nil - } - *s = make(Sensors, 0, len(v)) - for k, v := range v { - *s = append(*s, sensors.TemperatureStat{ - SensorKey: k, - Temperature: v["temperature"].(float64), - High: v["high"].(float64), - Critical: v["critical"].(float64), - }) - } - return nil -} - func (result Aggregated) MarshalJSON() ([]byte, error) { - buf := bufPool.Get() - defer bufPool.Put(buf) + if len(result.Entries) == 0 { + return []byte("[]"), nil + } + + capacity := 10 * 1024 + if result.Mode == SystemInfoAggregateModeSensorTemperature { + // give each sensor key 30 bytes per entry per sensor key + capacity = 30 * len(result.Entries) * len(result.Entries[0]) + } + buf := make([]byte, 0, capacity) buf = append(buf, '[') i := 0 - n := len(result) - for _, entry := range result { + n := len(result.Entries) + for _, entry := range result.Entries { buf = append(buf, '{') j := 0 m := len(entry) @@ -178,10 +158,12 @@ func (result Aggregated) MarshalJSON() ([]byte, error) { switch v := v.(type) { case float64: buf = strconv.AppendFloat(buf, v, 'f', 2, 64) - case uint64: - buf = strconv.AppendUint(buf, v, 10) + case int32: + buf = strconv.AppendInt(buf, int64(v), 10) case int64: buf = strconv.AppendInt(buf, v, 10) + case uint64: + buf = strconv.AppendUint(buf, v, 10) default: panic(fmt.Sprintf("unexpected type: %T", v)) } diff --git a/internal/metrics/systeminfo/system_info.go b/internal/metrics/systeminfo/system_info.go index 9e291b40..cbd1e990 100644 --- a/internal/metrics/systeminfo/system_info.go +++ b/internal/metrics/systeminfo/system_info.go @@ -24,7 +24,11 @@ import ( type ( Sensors []sensors.TemperatureStat // @name Sensors - Aggregated []map[string]any + Aggregated struct { + Entries []map[string]any + Mode SystemInfoAggregateMode + } + AggregatedJSON []map[string]any ) type SystemInfo struct { @@ -218,37 +222,40 @@ func (s *SystemInfo) collectSensorsInfo(ctx context.Context) error { // recharts friendly. func aggregate(entries []*SystemInfo, query url.Values) (total int, result Aggregated) { n := len(entries) - aggregated := make(Aggregated, 0, n) - switch SystemInfoAggregateMode(query.Get("aggregate")) { + aggregated := Aggregated{ + Entries: make([]map[string]any, n), + Mode: SystemInfoAggregateMode(query.Get("aggregate")), + } + switch aggregated.Mode { case SystemInfoAggregateModeCPUAverage: - for _, entry := range entries { + for i, entry := range entries { if entry.CPUAverage != nil { - aggregated = append(aggregated, map[string]any{ + aggregated.Entries[i] = map[string]any{ "timestamp": entry.Timestamp, "cpu_average": *entry.CPUAverage, - }) + } } } case SystemInfoAggregateModeMemoryUsage: - for _, entry := range entries { + for i, entry := range entries { if entry.Memory != nil { - aggregated = append(aggregated, map[string]any{ + aggregated.Entries[i] = map[string]any{ "timestamp": entry.Timestamp, "memory_usage": entry.Memory.Used, - }) + } } } case SystemInfoAggregateModeMemoryUsagePercent: - for _, entry := range entries { + for i, entry := range entries { if entry.Memory != nil { - aggregated = append(aggregated, map[string]any{ + aggregated.Entries[i] = map[string]any{ "timestamp": entry.Timestamp, "memory_usage_percent": entry.Memory.UsedPercent, - }) + } } } case SystemInfoAggregateModeDisksReadSpeed: - for _, entry := range entries { + for i, entry := range entries { if entry.DisksIO == nil { continue } @@ -257,10 +264,10 @@ func aggregate(entries []*SystemInfo, query url.Values) (total int, result Aggre m[name] = usage.ReadSpeed } m["timestamp"] = entry.Timestamp - aggregated = append(aggregated, m) + aggregated.Entries[i] = m } case SystemInfoAggregateModeDisksWriteSpeed: - for _, entry := range entries { + for i, entry := range entries { if entry.DisksIO == nil { continue } @@ -269,10 +276,10 @@ func aggregate(entries []*SystemInfo, query url.Values) (total int, result Aggre m[name] = usage.WriteSpeed } m["timestamp"] = entry.Timestamp - aggregated = append(aggregated, m) + aggregated.Entries[i] = m } case SystemInfoAggregateModeDisksIOPS: - for _, entry := range entries { + for i, entry := range entries { if entry.DisksIO == nil { continue } @@ -281,10 +288,10 @@ func aggregate(entries []*SystemInfo, query url.Values) (total int, result Aggre m[name] = usage.Iops } m["timestamp"] = entry.Timestamp - aggregated = append(aggregated, m) + aggregated.Entries[i] = m } case SystemInfoAggregateModeDiskUsage: - for _, entry := range entries { + for i, entry := range entries { if entry.Disks == nil { continue } @@ -293,32 +300,32 @@ func aggregate(entries []*SystemInfo, query url.Values) (total int, result Aggre m[name] = disk.Used } m["timestamp"] = entry.Timestamp - aggregated = append(aggregated, m) + aggregated.Entries[i] = m } case SystemInfoAggregateModeNetworkSpeed: - for _, entry := range entries { + for i, entry := range entries { if entry.Network == nil { continue } - aggregated = append(aggregated, map[string]any{ + aggregated.Entries[i] = map[string]any{ "timestamp": entry.Timestamp, "upload": entry.Network.UploadSpeed, "download": entry.Network.DownloadSpeed, - }) + } } case SystemInfoAggregateModeNetworkTransfer: - for _, entry := range entries { + for i, entry := range entries { if entry.Network == nil { continue } - aggregated = append(aggregated, map[string]any{ + aggregated.Entries[i] = map[string]any{ "timestamp": entry.Timestamp, "upload": entry.Network.BytesSent, "download": entry.Network.BytesRecv, - }) + } } case SystemInfoAggregateModeSensorTemperature: - for _, entry := range entries { + for i, entry := range entries { if entry.Sensors == nil { continue } @@ -327,12 +334,12 @@ func aggregate(entries []*SystemInfo, query url.Values) (total int, result Aggre m[sensor.SensorKey] = sensor.Temperature } m["timestamp"] = entry.Timestamp - aggregated = append(aggregated, m) + aggregated.Entries[i] = m } default: - return -1, nil + return -1, Aggregated{} } - return len(aggregated), aggregated + return len(aggregated.Entries), aggregated } func diff(x, y uint64) uint64 {