Merge branch 'nezhahq:master' into master

This commit is contained in:
77-QiQi authored and GitHub committed 2026-09-13 20:53:20 +08:00
commit b18e085a2c
19 files changed
+773 -138

No files matched your search

+3 -2
View File
@@ -119,7 +119,7 @@ add your theme to [service/singleton/frontend-templates.yaml](service/singleton/
<a href="https://github.com/DarcJC" title="Darc Z."><img src="https://avatars.githubusercontent.com/u/53445798?v=4" width="50;" alt="Darc Z."/></a>
<a href="https://github.com/Creling" title="Creling"><img src="https://avatars.githubusercontent.com/u/43109504?v=4" width="50;" alt="Creling"/></a>
<a href="https://github.com/coreff" title="Core F"><img src="https://avatars.githubusercontent.com/u/38347122?v=4" width="50;" alt="Core F"/></a>
<a href="https://github.com/wellcoming" title="Coming"><img src="https://avatars.githubusercontent.com/u/74850890?v=4" width="50;" alt="Coming"/></a>
<a href="https://github.com/NikoCat233" title="NikoCat233"><img src="https://avatars.githubusercontent.com/u/139348239?v=4" width="50;" alt="NikoCat233"/></a>
<a href="https://github.com/adminsama" title="adminsama"><img src="https://avatars.githubusercontent.com/u/60880076?v=4" width="50;" alt="adminsama"/></a>
<a href="https://github.com/acgpiano" title="Acgpiano"><img src="https://avatars.githubusercontent.com/u/15900800?v=4" width="50;" alt="Acgpiano"/></a>
<a href="https://github.com/eya46" title="eya46"><img src="https://avatars.githubusercontent.com/u/61458340?v=4" width="50;" alt="eya46"/></a>
@@ -136,6 +136,7 @@ add your theme to [service/singleton/frontend-templates.yaml](service/singleton/
<a href="https://github.com/arkylin" title="凌"><img src="https://avatars.githubusercontent.com/u/35104502?v=4" width="50;" alt="凌"/></a>
<a href="https://github.com/yumusb" title="榆木"><img src="https://avatars.githubusercontent.com/u/43062104?v=4" width="50;" alt="榆木"/></a>
<a href="https://github.com/colour93" title="93"><img src="https://avatars.githubusercontent.com/u/64313711?v=4" width="50;" alt="93"/></a>
<a href="https://github.com/wellcoming" title="Coming Wel"><img src="https://avatars.githubusercontent.com/u/74850890?v=4" width="50;" alt="Coming Wel"/></a>
<a href="https://github.com/wwng2333" title=":D"><img src="https://avatars.githubusercontent.com/u/17147265?v=4" width="50;" alt=":D"/></a>
<a href="https://github.com/Es-dese" title="Esdese"><img src="https://avatars.githubusercontent.com/u/71542548?v=4" width="50;" alt="Esdese"/></a>
<a href="https://github.com/GreenTeodoro839" title="GreenTeodoro839"><img src="https://avatars.githubusercontent.com/u/77104800?v=4" width="50;" alt="GreenTeodoro839"/></a>
@@ -145,7 +146,6 @@ add your theme to [service/singleton/frontend-templates.yaml](service/singleton/
<a href="https://github.com/MartijnLindeman" title="Martijn Lindeman"><img src="https://avatars.githubusercontent.com/u/78365708?v=4" width="50;" alt="Martijn Lindeman"/></a>
<a href="https://github.com/silver-ymz" title="Mingzhuo Yin"><img src="https://avatars.githubusercontent.com/u/78400701?v=4" width="50;" alt="Mingzhuo Yin"/></a>
<a href="https://github.com/dreamingsleeping" title="Nanjing Hopefun Network Technology Co. Ltd."><img src="https://avatars.githubusercontent.com/u/13828658?v=4" width="50;" alt="Nanjing Hopefun Network Technology Co. Ltd."/></a>
<a href="https://github.com/NikoCat233" title="NikoCat233"><img src="https://avatars.githubusercontent.com/u/139348239?v=4" width="50;" alt="NikoCat233"/></a>
<a href="https://github.com/Septrum101" title="Spetrum"><img src="https://avatars.githubusercontent.com/u/11692994?v=4" width="50;" alt="Spetrum"/></a>
<a href="https://github.com/IamTaoChen" title="Tao Chen"><img src="https://avatars.githubusercontent.com/u/42793494?v=4" width="50;" alt="Tao Chen"/></a>
<a href="https://github.com/nickfox-taterli" title="Tater Li"><img src="https://avatars.githubusercontent.com/u/19658596?v=4" width="50;" alt="Tater Li"/></a>
@@ -155,6 +155,7 @@ add your theme to [service/singleton/frontend-templates.yaml](service/singleton/
<a href="https://github.com/77-QiQi" title="77-QiQi"><img src="https://avatars.githubusercontent.com/u/87052253?v=4" width="50;" alt="77-QiQi"/></a>
<a href="https://github.com/railzen" title="Chra"><img src="https://avatars.githubusercontent.com/u/48409318?v=4" width="50;" alt="Chra"/></a>
<a href="https://github.com/Mubelotix" title="Mubelotix"><img src="https://avatars.githubusercontent.com/u/42696684?v=4" width="50;" alt="Mubelotix"/></a>
<a href="https://github.com/ryrenz" title="Ryrenz"><img src="https://avatars.githubusercontent.com/u/163799701?v=4" width="50;" alt="Ryrenz"/></a>
<a href="https://github.com/zhdsmy" title="zhdsmy"><img src="https://avatars.githubusercontent.com/u/8348149?v=4" width="50;" alt="zhdsmy"/></a><!--GAMFC_DELIMITER_END-->
## Special Thanks
+1 -1
View File
@@ -152,7 +152,7 @@ func identityHandler() func(c *gin.Context) any {
return nil
}
currentIP := c.GetString(model.CtxKeyRealIPStr)
if sess.IP != currentIP {
if !singleton.Conf.JWTIPChangeAllowed() && sess.IP != currentIP {
c.Set(model.CtxKeyIsIPMismatch, true)
return nil
}
@@ -193,6 +193,61 @@ func TestIdentityHandlerFlagsIPMismatch(t *testing.T) {
assert.True(t, verify.GetBool(model.CtxKeyIsIPMismatch))
}
func TestIdentityHandlerAllowsIPMismatchWhenConfigured(t *testing.T) {
cleanup := setupJWTSessionTest(t)
defer cleanup()
singleton.Conf.SetJWTIPChangeAllowed(true)
ctx := newCtxForUser(0, "1.2.3.4", "ua")
user := model.User{Common: model.Common{ID: 100}, TokenVersion: 7}
claims, err := issueJWTSession(ctx, &user, 1)
require.NoError(t, err)
verify := newCtxForUser(0, "9.9.9.9", "ua")
verify.Set("JWT_PAYLOAD", jwt.MapClaims{
jwtClaimUserID: claims[jwtClaimUserID],
jwtClaimKeyID: claims[jwtClaimKeyID],
})
identity := identityHandler()(verify)
require.NotNil(t, identity, "IP mismatch must be accepted when allow_jwt_ip_change is enabled")
assert.False(t, verify.GetBool(model.CtxKeyIsIPMismatch))
}
func TestIdentityHandlerConcurrentIPChangeSetting(t *testing.T) {
cleanup := setupJWTSessionTest(t)
defer cleanup()
ctx := newCtxForUser(0, "1.2.3.4", "ua")
user := model.User{Common: model.Common{ID: 100}, TokenVersion: 7}
claims, err := issueJWTSession(ctx, &user, 1)
require.NoError(t, err)
stop, done := make(chan struct{}), make(chan struct{})
go func() {
defer close(done)
for {
select {
case <-stop:
return
default:
singleton.Conf.SetJWTIPChangeAllowed(true)
singleton.Conf.SetJWTIPChangeAllowed(false)
}
}
}()
defer func() {
close(stop)
<-done
}()
for i := 0; i < 100; i++ {
verify := newCtxForUser(0, "1.2.3.4", "ua")
verify.Set("JWT_PAYLOAD", jwt.MapClaims(claims))
require.NotNil(t, identityHandler()(verify), "matching IP must pass during setting updates")
}
}
func TestIdentityHandlerRejectsUnknownKeyID(t *testing.T) {
cleanup := setupJWTSessionTest(t)
defer cleanup()
+9
View File
@@ -97,6 +97,9 @@ func updateConfig(c *gin.Context) (any, error) {
singleton.Conf.EnableIPChangeNotification = sf.EnableIPChangeNotification
singleton.Conf.EnablePlainIPInNotification = sf.EnablePlainIPInNotification
if sf.AllowJWTIPChange != nil {
singleton.Conf.SetJWTIPChangeAllowed(*sf.AllowJWTIPChange)
}
singleton.Conf.Cover = sf.Cover
singleton.Conf.InstallHost = sf.InstallHost
singleton.Conf.DashboardHost = sf.DashboardHost
@@ -160,6 +163,12 @@ func fireMCPKillSwitch() {
// the current config to avoid accidentally tripping the kill switch on
// partial PATCH calls that omit enable_mcp.
func resolveSettingEnableMCP(formValue *bool, current bool) bool {
return resolveOptionalBool(formValue, current)
}
// resolveOptionalBool preserves a setting for partial PATCH requests when its
// boolean field is absent, while still allowing an explicit false value.
func resolveOptionalBool(formValue *bool, current bool) bool {
if formValue == nil {
return current
}
@@ -0,0 +1,26 @@
package controller
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
"github.com/nezhahq/nezha/model"
)
func TestSettingForm_AllowJWTIPChangePreservesAbsentAndDecodesExplicitValues(t *testing.T) {
var omitted model.SettingForm
require.NoError(t, json.Unmarshal([]byte(`{"site_name":"X"}`), &omitted))
require.Nil(t, omitted.AllowJWTIPChange)
var enabled model.SettingForm
require.NoError(t, json.Unmarshal([]byte(`{"allow_jwt_ip_change":true}`), &enabled))
require.NotNil(t, enabled.AllowJWTIPChange)
require.True(t, *enabled.AllowJWTIPChange)
var disabled model.SettingForm
require.NoError(t, json.Unmarshal([]byte(`{"allow_jwt_ip_change":false}`), &disabled))
require.NotNil(t, disabled.AllowJWTIPChange)
require.False(t, *disabled.AllowJWTIPChange)
}
+21
View File
@@ -61,6 +61,9 @@ type ConfigDashboard struct {
AdminTemplate string `koanf:"admin_template" json:"admin_template,omitempty"`
EnablePlainIPInNotification bool `koanf:"enable_plain_ip_in_notification" json:"enable_plain_ip_in_notification,omitempty"` // 通知信息IP不打码
// AllowJWTIPChange disables the IP binding check for browser login sessions.
// Keep this disabled by default so a stolen JWT remains bound to its issuing IP.
AllowJWTIPChange bool `koanf:"allow_jwt_ip_change" json:"allow_jwt_ip_change,omitempty"`
EnableMCP bool `koanf:"enable_mcp" json:"enable_mcp,omitempty"` // 是否启用 MCP 入口(默认关闭;启用前请审视 PAT scope/whitelist)
@@ -103,6 +106,10 @@ type Config struct {
// SettingResponse 按值拷贝 ConfigDashboard 触发 copylocks。
mcpEnabled atomic.Bool `koanf:"-" json:"-" yaml:"-"`
// jwtIPChangeAllowed mirrors AllowJWTIPChange for concurrent authentication
// and setting updates. Keep atomic fields outside ConfigDashboard.
jwtIPChangeAllowed atomic.Bool `koanf:"-" json:"-" yaml:"-"`
// oauth2 配置
Oauth2 map[string]*Oauth2Config `koanf:"oauth2" json:"oauth2,omitempty"`
@@ -225,6 +232,8 @@ func (c *Config) Read(path string, frontendTemplates []FrontendTemplate) error {
c.JWTTimeout = 1
}
c.jwtIPChangeAllowed.Store(c.AllowJWTIPChange)
if c.AgentSecretKey == "" {
c.AgentSecretKey, err = utils.GenerateRandomString(32)
if err != nil {
@@ -252,6 +261,17 @@ func (c *Config) SetMCPEnabled(v bool) {
c.mcpEnabled.Store(v)
}
// JWTIPChangeAllowed reports whether JWT sessions may change IP concurrently.
func (c *Config) JWTIPChangeAllowed() bool {
return c.jwtIPChangeAllowed.Load()
}
// SetJWTIPChangeAllowed updates only the atomic mirror. save() synchronizes
// the public field for persistence; authentication must use the accessor.
func (c *Config) SetJWTIPChangeAllowed(v bool) {
c.jwtIPChangeAllowed.Store(v)
}
// Save 保存配置文件
func (c *Config) Save() error {
return c.save()
@@ -320,6 +340,7 @@ func (c *Config) patchYAMLField(key string, value any) error {
func (c *Config) save() error {
c.EnableMCP = c.mcpEnabled.Load()
c.AllowJWTIPChange = c.jwtIPChangeAllowed.Load()
data, err := yaml.Marshal(c)
if err != nil {
return err
+3 -1
View File
@@ -24,6 +24,7 @@ func TestReadConfig(t *testing.T) {
{"user_template", c.UserTemplate, c.UserTemplate == "user-dist"},
{"admin_template", c.AdminTemplate, c.AdminTemplate == "admin-dist"},
{"agent_secret_key", c.AgentSecretKey, c.AgentSecretKey != ""},
{"allow_jwt_ip_change", c.AllowJWTIPChange, !c.AllowJWTIPChange},
}
for _, field := range testFields {
@@ -36,7 +37,7 @@ func TestReadConfig(t *testing.T) {
})
t.Run("ReadFile", func(t *testing.T) {
const testCfg = "jwt_secret_key: test\nuser_template: um\nadmin_template: am\nagent_secret_key: none\nsite_name: lowkick"
const testCfg = "jwt_secret_key: test\nuser_template: um\nadmin_template: am\nagent_secret_key: none\nsite_name: lowkick\nallow_jwt_ip_change: true"
var testFrontendTemplates = []FrontendTemplate{
{Path: "um"},
@@ -59,6 +60,7 @@ func TestReadConfig(t *testing.T) {
{"admin_template", c.AdminTemplate, c.AdminTemplate == "am"},
{"agent_secret_key", c.AgentSecretKey, c.AgentSecretKey == "none"},
{"site_name", c.SiteName, c.SiteName == "lowkick"},
{"allow_jwt_ip_change", c.AllowJWTIPChange, c.AllowJWTIPChange},
}
for _, field := range testFields {
+30
View File
@@ -35,9 +35,28 @@ type HostState struct {
ProcessCount uint64 `json:"process_count,omitempty"`
Temperatures []SensorTemperature `json:"temperatures,omitempty"`
GPU []float64 `json:"gpu,omitempty"`
GPUs []GPUStat `json:"gpus,omitempty"`
}
// GPUStat carries per-card figures, index-aligned with Host.GPU. Memory is in
// MiB and stays zero for vendors that do not report it, so MemoryTotal == 0
// means "unknown" rather than "no memory".
type GPUStat struct {
Utilization float64 `json:"utilization"`
MemoryUsed uint64 `json:"memory_used,omitempty"`
MemoryTotal uint64 `json:"memory_total,omitempty"`
}
func (s *HostState) PB() *pb.State {
gs := make([]*pb.State_GPU, 0, len(s.GPUs))
for _, g := range s.GPUs {
gs = append(gs, &pb.State_GPU{
Utilization: g.Utilization,
MemoryUsed: g.MemoryUsed,
MemoryTotal: g.MemoryTotal,
})
}
var ts []*pb.State_SensorTemperature
for _, t := range s.Temperatures {
ts = append(ts, &pb.State_SensorTemperature{
@@ -64,10 +83,20 @@ func (s *HostState) PB() *pb.State {
ProcessCount: s.ProcessCount,
Temperatures: ts,
Gpu: s.GPU,
Gpus: gs,
}
}
func PB2State(s *pb.State) HostState {
var gs []GPUStat
for _, g := range s.GetGpus() {
gs = append(gs, GPUStat{
Utilization: g.GetUtilization(),
MemoryUsed: g.GetMemoryUsed(),
MemoryTotal: g.GetMemoryTotal(),
})
}
var ts []SensorTemperature
for _, t := range s.GetTemperatures() {
ts = append(ts, SensorTemperature{
@@ -93,6 +122,7 @@ func PB2State(s *pb.State) HostState {
UdpConnCount: s.GetUdpConnCount(),
ProcessCount: s.GetProcessCount(),
Temperatures: ts,
GPUs: gs,
GPU: s.GetGpu(),
}
}
+56
View File
@@ -0,0 +1,56 @@
package model
import (
"path/filepath"
"sync"
"testing"
)
func TestJWTIPChangeAllowedPersistence(t *testing.T) {
c := &Config{}
if c.JWTIPChangeAllowed() {
t.Fatal("zero-value Config must bind JWT sessions to their IP")
}
// An absent agent secret makes Read save the config during initialization.
path := newTempConfig(t, "jwt_secret_key: test\nallow_jwt_ip_change: true\n")
if err := c.Read(path, nil); err != nil {
t.Fatal(err)
}
if !c.JWTIPChangeAllowed() {
t.Fatal("Read must initialize the atomic mirror before saving")
}
for _, want := range []bool{true, false} {
c.SetJWTIPChangeAllowed(want)
if err := c.Save(); err != nil {
t.Fatal(err)
}
reloaded := &Config{}
if err := reloaded.Read(path, nil); err != nil {
t.Fatal(err)
}
if c.AllowJWTIPChange != want || reloaded.JWTIPChangeAllowed() != want {
t.Fatalf("saved and reloaded setting must be %v", want)
}
}
}
func TestJWTIPChangeAllowedConcurrentAccess(t *testing.T) {
c := &Config{filePath: filepath.Join(t.TempDir(), "config.yaml")}
var wg sync.WaitGroup
wg.Go(func() {
for i := 0; i < 10000; i++ {
c.SetJWTIPChangeAllowed(i%2 == 0)
}
})
wg.Go(func() {
for i := 0; i < 10000; i++ {
_ = c.JWTIPChangeAllowed()
}
})
for i := 0; i < 20; i++ {
if err := c.Save(); err != nil {
t.Error(err)
}
}
wg.Wait()
}
+1
View File
@@ -19,6 +19,7 @@ type SettingForm struct {
AgentTLS bool `json:"tls,omitempty" validate:"optional"`
EnableIPChangeNotification bool `json:"enable_ip_change_notification,omitempty" validate:"optional"`
EnablePlainIPInNotification bool `json:"enable_plain_ip_in_notification,omitempty" validate:"optional"`
AllowJWTIPChange *bool `json:"allow_jwt_ip_change,omitempty" validate:"optional"`
EnableMCP *bool `json:"enable_mcp,omitempty" validate:"optional"`
}
+124
View File
@@ -0,0 +1,124 @@
package tsdb
import (
"path/filepath"
"sync/atomic"
"testing"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type testReadOnlyChecker struct {
value atomic.Bool
}
func (c *testReadOnlyChecker) IsReadOnly() bool {
return c.value.Load()
}
func newDiskGuardTestDB(t *testing.T) *TSDB {
t.Helper()
db, err := Open(&Config{
DataPath: filepath.Join(t.TempDir(), "tsdb"),
RetentionDays: 1,
MinFreeDiskSpaceGB: 1,
DedupInterval: time.Second,
})
require.NoError(t, err)
t.Cleanup(func() { require.NoError(t, db.Close()) })
return db
}
func bufferedRows(w *bufferedWriter) int {
w.mu.Lock()
defer w.mu.Unlock()
return len(w.buffer)
}
func TestReadOnlyStorageDropsWritesAndKeepsQueriesAvailable(t *testing.T) {
db := newDiskGuardTestDB(t)
checker := &testReadOnlyChecker{}
db.readOnly = checker
originalAddRows := db.addRowsFn
var addRowsCalls atomic.Int32
db.addRowsFn = func(rows []storage.MetricRow, precisionBits uint8) {
addRowsCalls.Add(1)
originalAddRows(rows, precisionBits)
}
firstTimestamp := time.Now().Add(-time.Minute)
require.NoError(t, db.WriteServerMetrics(&ServerMetrics{
ServerID: 1,
Timestamp: firstTimestamp,
CPU: 10,
}))
db.Flush()
require.Equal(t, int32(1), addRowsCalls.Load())
before, err := db.QueryServerMetrics(1, MetricServerCPU, Period1Day)
require.NoError(t, err)
require.NotEmpty(t, before)
checker.value.Store(true)
require.NoError(t, db.WriteServerMetrics(&ServerMetrics{
ServerID: 1,
Timestamp: firstTimestamp.Add(2 * time.Second),
CPU: 20,
}))
assert.True(t, db.WritesPaused())
assert.Zero(t, bufferedRows(db.writer), "read-only writes must not accumulate in memory")
assert.Equal(t, int32(1), addRowsCalls.Load(), "read-only samples must not reach VictoriaMetrics")
afterDrop, err := db.QueryServerMetrics(1, MetricServerCPU, Period1Day)
require.NoError(t, err)
assert.Equal(t, before, afterDrop, "read-only mode must preserve existing query access")
checker.value.Store(false)
require.NoError(t, db.WriteServerMetrics(&ServerMetrics{
ServerID: 1,
Timestamp: firstTimestamp.Add(4 * time.Second),
CPU: 30,
}))
db.Flush()
assert.False(t, db.WritesPaused())
assert.Equal(t, int32(2), addRowsCalls.Load(), "writes must resume after storage leaves read-only mode")
}
func TestDiskFullAddRowsPanicPausesWrites(t *testing.T) {
db := newDiskGuardTestDB(t)
db.addRowsFn = func([]storage.MetricRow, uint8) {
panic("write data: no space left on device")
}
require.NoError(t, db.WriteServerMetrics(&ServerMetrics{
ServerID: 1,
Timestamp: time.Now(),
CPU: 10,
}))
assert.NotPanics(t, db.Flush)
assert.True(t, db.WritesPaused())
require.NoError(t, db.WriteServerMetrics(&ServerMetrics{
ServerID: 1,
Timestamp: time.Now().Add(time.Second),
CPU: 20,
}))
assert.Zero(t, bufferedRows(db.writer))
}
func TestNonDiskStoragePanicIsNotHidden(t *testing.T) {
db := newDiskGuardTestDB(t)
db.addRowsFn = func([]storage.MetricRow, uint8) {
panic("storage invariant failed")
}
require.NoError(t, db.WriteServerMetrics(&ServerMetrics{
ServerID: 1,
Timestamp: time.Now(),
CPU: 10,
}))
assert.PanicsWithValue(t, "storage invariant failed", db.Flush)
}
+4 -1
View File
@@ -10,8 +10,11 @@ func (db *TSDB) Maintenance() {
if db.closed {
return
}
if !db.acceptsWrites() {
return
}
log.Println("NEZHA>> TSDB starting maintenance (flush)...")
db.storage.DebugFlush()
db.debugFlushSafely()
log.Println("NEZHA>> TSDB maintenance completed")
}
+123 -5
View File
@@ -1,15 +1,26 @@
package tsdb
import (
"errors"
"fmt"
"log"
"path/filepath"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/storage"
)
// ErrDiskFull is returned when VictoriaMetrics cannot initialize because the
// filesystem containing the TSDB has run out of space.
var ErrDiskFull = errors.New("TSDB disk is full")
type readOnlyChecker interface {
IsReadOnly() bool
}
// TSDB 封装 VictoriaMetrics 存储
type TSDB struct {
storage *storage.Storage
@@ -18,6 +29,13 @@ type TSDB struct {
closed bool
writer *bufferedWriter
readOnly readOnlyChecker
addRowsFn func([]storage.MetricRow, uint8)
debugFlushFn func()
readOnlyObserved atomic.Bool
diskFullPaused atomic.Bool
}
// InitGlobalSettings 初始化 VictoriaMetrics 包级别的全局设置。
@@ -35,7 +53,18 @@ func InitGlobalSettings(config *Config) {
}
// Open 打开或创建 TSDB 存储
func Open(config *Config) (*TSDB, error) {
func Open(config *Config) (db *TSDB, err error) {
defer func() {
if recovered := recover(); recovered != nil {
if diskErr := diskFullPanicError(recovered); diskErr != nil {
db = nil
err = diskErr
return
}
panic(recovered)
}
}()
if config == nil {
config = DefaultConfig()
}
@@ -59,9 +88,12 @@ func Open(config *Config) (*TSDB, error) {
stor := storage.MustOpenStorage(dataPath, opts)
db := &TSDB{
storage: stor,
config: config,
db = &TSDB{
storage: stor,
config: config,
readOnly: stor,
addRowsFn: stor.AddRows,
debugFlushFn: stor.DebugFlush,
}
db.writer = newBufferedWriter(db, config.WriteBufferSize, config.WriteBufferFlushInterval)
@@ -72,6 +104,86 @@ func Open(config *Config) (*TSDB, error) {
return db, nil
}
func diskFullPanicError(recovered any) error {
message := strings.ToLower(fmt.Sprint(recovered))
for _, marker := range []string{
"no space left on device",
"disk quota exceeded",
"not enough space on the disk",
} {
if strings.Contains(message, marker) {
return fmt.Errorf("%w: %v", ErrDiskFull, recovered)
}
}
return nil
}
// WritesPaused reports whether new samples are currently being discarded.
// Queries remain available while VictoriaMetrics is in read-only mode.
func (db *TSDB) WritesPaused() bool {
if db.diskFullPaused.Load() {
return true
}
return db.readOnly != nil && db.readOnly.IsReadOnly()
}
func (db *TSDB) acceptsWrites() bool {
if db.diskFullPaused.Load() {
return false
}
readOnly := db.readOnly != nil && db.readOnly.IsReadOnly()
if readOnly {
if db.readOnlyObserved.CompareAndSwap(false, true) {
log.Println("NEZHA>> TSDB writes paused because free disk space is below the configured limit; dashboard, queries, and alerts remain available")
}
return false
}
if db.readOnlyObserved.CompareAndSwap(true, false) {
log.Println("NEZHA>> TSDB writes resumed after free disk space recovered")
}
return true
}
func (db *TSDB) pauseWritesAfterDiskFull(recovered any) {
if db.diskFullPaused.CompareAndSwap(false, true) {
log.Printf("NEZHA>> TSDB disk is full; disabling TSDB writes until dashboard restart while keeping dashboard, queries, and alerts running: %v", recovered)
}
}
func (db *TSDB) addRowsSafely(rows []storage.MetricRow) {
if len(rows) == 0 || !db.acceptsWrites() {
return
}
defer func() {
if recovered := recover(); recovered != nil {
if diskFullPanicError(recovered) == nil {
panic(recovered)
}
db.pauseWritesAfterDiskFull(recovered)
}
}()
db.addRowsFn(rows, 64)
}
func (db *TSDB) debugFlushSafely() {
if !db.acceptsWrites() {
return
}
defer func() {
if recovered := recover(); recovered != nil {
if diskFullPanicError(recovered) == nil {
panic(recovered)
}
db.pauseWritesAfterDiskFull(recovered)
}
}()
db.debugFlushFn()
}
// Close 关闭 TSDB 存储
func (db *TSDB) Close() error {
db.mu.Lock()
@@ -110,8 +222,14 @@ func (db *TSDB) IsClosed() bool {
// Flush 强制刷盘(主要用于测试)
func (db *TSDB) Flush() {
db.mu.RLock()
defer db.mu.RUnlock()
if db.closed {
return
}
if db.writer != nil {
db.writer.flush()
}
db.storage.DebugFlush()
db.debugFlushSafely()
}
+17 -6
View File
@@ -47,18 +47,29 @@ func (w *bufferedWriter) flushLoop() {
}
func (w *bufferedWriter) write(rows []storage.MetricRow) {
if !w.db.acceptsWrites() {
w.discard()
return
}
w.mu.Lock()
w.buffer = append(w.buffer, rows...)
if len(w.buffer) >= w.maxSize {
rows := w.buffer
w.buffer = make([]storage.MetricRow, 0, w.maxSize)
w.mu.Unlock()
w.db.storage.AddRows(rows, 64)
w.db.addRowsSafely(rows)
return
}
w.mu.Unlock()
}
func (w *bufferedWriter) discard() {
w.mu.Lock()
w.buffer = make([]storage.MetricRow, 0, w.maxSize)
w.mu.Unlock()
}
func (w *bufferedWriter) flush() {
w.mu.Lock()
if len(w.buffer) == 0 {
@@ -69,7 +80,7 @@ func (w *bufferedWriter) flush() {
w.buffer = make([]storage.MetricRow, 0, w.maxSize)
w.mu.Unlock()
w.db.storage.AddRows(rows, 64)
w.db.addRowsSafely(rows)
}
func (w *bufferedWriter) stop() {
@@ -171,7 +182,7 @@ func (db *TSDB) WriteServerMetrics(m *ServerMetrics) error {
if db.writer != nil {
db.writer.write(rows)
} else {
db.storage.AddRows(rows, 64)
db.addRowsSafely(rows)
}
return nil
}
@@ -200,7 +211,7 @@ func (db *TSDB) WriteServiceMetrics(m *ServiceMetrics) error {
if db.writer != nil {
db.writer.write(rows)
} else {
db.storage.AddRows(rows, 64)
db.addRowsSafely(rows)
}
return nil
}
@@ -265,7 +276,7 @@ func (db *TSDB) WriteBatchServerMetrics(metrics []*ServerMetrics) error {
if db.writer != nil {
db.writer.write(rows)
} else {
db.storage.AddRows(rows, 64)
db.addRowsSafely(rows)
}
return nil
}
@@ -295,7 +306,7 @@ func (db *TSDB) WriteBatchServiceMetrics(metrics []*ServiceMetrics) error {
if db.writer != nil {
db.writer.write(rows)
} else {
db.storage.AddRows(rows, 64)
db.addRowsSafely(rows)
}
return nil
}
+216 -119
View File
@@ -169,6 +169,7 @@ type State struct {
ProcessCount uint64 `protobuf:"varint,15,opt,name=process_count,json=processCount,proto3" json:"process_count,omitempty"`
Temperatures []*State_SensorTemperature `protobuf:"bytes,16,rep,name=temperatures,proto3" json:"temperatures,omitempty"`
Gpu []float64 `protobuf:"fixed64,17,rep,packed,name=gpu,proto3" json:"gpu,omitempty"`
Gpus []*State_GPU `protobuf:"bytes,18,rep,name=gpus,proto3" json:"gpus,omitempty"`
}
func (x *State) Reset() {
@@ -322,6 +323,79 @@ func (x *State) GetGpu() []float64 {
return nil
}
func (x *State) GetGpus() []*State_GPU {
if x != nil {
return x.Gpus
}
return nil
}
// State_GPU carries per-card figures. Index-aligned with Host.gpu, matching
// the existing `gpu` field it supersedes; that field stays populated so older
// dashboards keep working.
type State_GPU struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Utilization float64 `protobuf:"fixed64,1,opt,name=utilization,proto3" json:"utilization,omitempty"`
MemoryUsed uint64 `protobuf:"varint,2,opt,name=memory_used,json=memoryUsed,proto3" json:"memory_used,omitempty"`
MemoryTotal uint64 `protobuf:"varint,3,opt,name=memory_total,json=memoryTotal,proto3" json:"memory_total,omitempty"`
}
func (x *State_GPU) Reset() {
*x = State_GPU{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_nezha_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *State_GPU) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*State_GPU) ProtoMessage() {}
func (x *State_GPU) ProtoReflect() protoreflect.Message {
mi := &file_proto_nezha_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use State_GPU.ProtoReflect.Descriptor instead.
func (*State_GPU) Descriptor() ([]byte, []int) {
return file_proto_nezha_proto_rawDescGZIP(), []int{2}
}
func (x *State_GPU) GetUtilization() float64 {
if x != nil {
return x.Utilization
}
return 0
}
func (x *State_GPU) GetMemoryUsed() uint64 {
if x != nil {
return x.MemoryUsed
}
return 0
}
func (x *State_GPU) GetMemoryTotal() uint64 {
if x != nil {
return x.MemoryTotal
}
return 0
}
type State_SensorTemperature struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -334,7 +408,7 @@ type State_SensorTemperature struct {
func (x *State_SensorTemperature) Reset() {
*x = State_SensorTemperature{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_nezha_proto_msgTypes[2]
mi := &file_proto_nezha_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -347,7 +421,7 @@ func (x *State_SensorTemperature) String() string {
func (*State_SensorTemperature) ProtoMessage() {}
func (x *State_SensorTemperature) ProtoReflect() protoreflect.Message {
mi := &file_proto_nezha_proto_msgTypes[2]
mi := &file_proto_nezha_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -360,7 +434,7 @@ func (x *State_SensorTemperature) ProtoReflect() protoreflect.Message {
// Deprecated: Use State_SensorTemperature.ProtoReflect.Descriptor instead.
func (*State_SensorTemperature) Descriptor() ([]byte, []int) {
return file_proto_nezha_proto_rawDescGZIP(), []int{2}
return file_proto_nezha_proto_rawDescGZIP(), []int{3}
}
func (x *State_SensorTemperature) GetName() string {
@@ -390,7 +464,7 @@ type Task struct {
func (x *Task) Reset() {
*x = Task{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_nezha_proto_msgTypes[3]
mi := &file_proto_nezha_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -403,7 +477,7 @@ func (x *Task) String() string {
func (*Task) ProtoMessage() {}
func (x *Task) ProtoReflect() protoreflect.Message {
mi := &file_proto_nezha_proto_msgTypes[3]
mi := &file_proto_nezha_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -416,7 +490,7 @@ func (x *Task) ProtoReflect() protoreflect.Message {
// Deprecated: Use Task.ProtoReflect.Descriptor instead.
func (*Task) Descriptor() ([]byte, []int) {
return file_proto_nezha_proto_rawDescGZIP(), []int{3}
return file_proto_nezha_proto_rawDescGZIP(), []int{4}
}
func (x *Task) GetId() uint64 {
@@ -455,7 +529,7 @@ type TaskResult struct {
func (x *TaskResult) Reset() {
*x = TaskResult{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_nezha_proto_msgTypes[4]
mi := &file_proto_nezha_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -468,7 +542,7 @@ func (x *TaskResult) String() string {
func (*TaskResult) ProtoMessage() {}
func (x *TaskResult) ProtoReflect() protoreflect.Message {
mi := &file_proto_nezha_proto_msgTypes[4]
mi := &file_proto_nezha_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -481,7 +555,7 @@ func (x *TaskResult) ProtoReflect() protoreflect.Message {
// Deprecated: Use TaskResult.ProtoReflect.Descriptor instead.
func (*TaskResult) Descriptor() ([]byte, []int) {
return file_proto_nezha_proto_rawDescGZIP(), []int{4}
return file_proto_nezha_proto_rawDescGZIP(), []int{5}
}
func (x *TaskResult) GetId() uint64 {
@@ -530,7 +604,7 @@ type Receipt struct {
func (x *Receipt) Reset() {
*x = Receipt{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_nezha_proto_msgTypes[5]
mi := &file_proto_nezha_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -543,7 +617,7 @@ func (x *Receipt) String() string {
func (*Receipt) ProtoMessage() {}
func (x *Receipt) ProtoReflect() protoreflect.Message {
mi := &file_proto_nezha_proto_msgTypes[5]
mi := &file_proto_nezha_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -556,7 +630,7 @@ func (x *Receipt) ProtoReflect() protoreflect.Message {
// Deprecated: Use Receipt.ProtoReflect.Descriptor instead.
func (*Receipt) Descriptor() ([]byte, []int) {
return file_proto_nezha_proto_rawDescGZIP(), []int{5}
return file_proto_nezha_proto_rawDescGZIP(), []int{6}
}
func (x *Receipt) GetProced() bool {
@@ -577,7 +651,7 @@ type Uint64Receipt struct {
func (x *Uint64Receipt) Reset() {
*x = Uint64Receipt{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_nezha_proto_msgTypes[6]
mi := &file_proto_nezha_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -590,7 +664,7 @@ func (x *Uint64Receipt) String() string {
func (*Uint64Receipt) ProtoMessage() {}
func (x *Uint64Receipt) ProtoReflect() protoreflect.Message {
mi := &file_proto_nezha_proto_msgTypes[6]
mi := &file_proto_nezha_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -603,7 +677,7 @@ func (x *Uint64Receipt) ProtoReflect() protoreflect.Message {
// Deprecated: Use Uint64Receipt.ProtoReflect.Descriptor instead.
func (*Uint64Receipt) Descriptor() ([]byte, []int) {
return file_proto_nezha_proto_rawDescGZIP(), []int{6}
return file_proto_nezha_proto_rawDescGZIP(), []int{7}
}
func (x *Uint64Receipt) GetData() uint64 {
@@ -624,7 +698,7 @@ type IOStreamData struct {
func (x *IOStreamData) Reset() {
*x = IOStreamData{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_nezha_proto_msgTypes[7]
mi := &file_proto_nezha_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -637,7 +711,7 @@ func (x *IOStreamData) String() string {
func (*IOStreamData) ProtoMessage() {}
func (x *IOStreamData) ProtoReflect() protoreflect.Message {
mi := &file_proto_nezha_proto_msgTypes[7]
mi := &file_proto_nezha_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -650,7 +724,7 @@ func (x *IOStreamData) ProtoReflect() protoreflect.Message {
// Deprecated: Use IOStreamData.ProtoReflect.Descriptor instead.
func (*IOStreamData) Descriptor() ([]byte, []int) {
return file_proto_nezha_proto_rawDescGZIP(), []int{7}
return file_proto_nezha_proto_rawDescGZIP(), []int{8}
}
func (x *IOStreamData) GetData() []byte {
@@ -674,7 +748,7 @@ type GeoIP struct {
func (x *GeoIP) Reset() {
*x = GeoIP{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_nezha_proto_msgTypes[8]
mi := &file_proto_nezha_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -687,7 +761,7 @@ func (x *GeoIP) String() string {
func (*GeoIP) ProtoMessage() {}
func (x *GeoIP) ProtoReflect() protoreflect.Message {
mi := &file_proto_nezha_proto_msgTypes[8]
mi := &file_proto_nezha_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -700,7 +774,7 @@ func (x *GeoIP) ProtoReflect() protoreflect.Message {
// Deprecated: Use GeoIP.ProtoReflect.Descriptor instead.
func (*GeoIP) Descriptor() ([]byte, []int) {
return file_proto_nezha_proto_rawDescGZIP(), []int{8}
return file_proto_nezha_proto_rawDescGZIP(), []int{9}
}
func (x *GeoIP) GetUse6() bool {
@@ -743,7 +817,7 @@ type IP struct {
func (x *IP) Reset() {
*x = IP{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_nezha_proto_msgTypes[9]
mi := &file_proto_nezha_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -756,7 +830,7 @@ func (x *IP) String() string {
func (*IP) ProtoMessage() {}
func (x *IP) ProtoReflect() protoreflect.Message {
mi := &file_proto_nezha_proto_msgTypes[9]
mi := &file_proto_nezha_proto_msgTypes[10]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -769,7 +843,7 @@ func (x *IP) ProtoReflect() protoreflect.Message {
// Deprecated: Use IP.ProtoReflect.Descriptor instead.
func (*IP) Descriptor() ([]byte, []int) {
return file_proto_nezha_proto_rawDescGZIP(), []int{9}
return file_proto_nezha_proto_rawDescGZIP(), []int{10}
}
func (x *IP) GetIpv4() string {
@@ -810,7 +884,7 @@ var file_proto_nezha_proto_rawDesc = []byte{
0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x62, 0x6f, 0x6f, 0x74, 0x54, 0x69, 0x6d, 0x65,
0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28,
0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x67, 0x70,
0x75, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x67, 0x70, 0x75, 0x22, 0xa9, 0x04, 0x0a,
0x75, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x67, 0x70, 0x75, 0x22, 0xcf, 0x04, 0x0a,
0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x70, 0x75, 0x18, 0x01, 0x20,
0x01, 0x28, 0x01, 0x52, 0x03, 0x63, 0x70, 0x75, 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x65, 0x6d, 0x5f,
0x75, 0x73, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x6d, 0x65, 0x6d, 0x55,
@@ -845,65 +919,74 @@ var file_proto_nezha_proto_rawDesc = []byte{
0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x54, 0x65, 0x6d,
0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x52, 0x0c, 0x74, 0x65, 0x6d, 0x70, 0x65, 0x72,
0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x67, 0x70, 0x75, 0x18, 0x11, 0x20,
0x03, 0x28, 0x01, 0x52, 0x03, 0x67, 0x70, 0x75, 0x22, 0x4f, 0x0a, 0x17, 0x53, 0x74, 0x61, 0x74,
0x65, 0x5f, 0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x54, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74,
0x75, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x74, 0x65, 0x6d, 0x70, 0x65,
0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0b, 0x74, 0x65,
0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x3e, 0x0a, 0x04, 0x54, 0x61, 0x73,
0x6b, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69,
0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52,
0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x7a, 0x0a, 0x0a, 0x54, 0x61, 0x73,
0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18,
0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x64,
0x65, 0x6c, 0x61, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x05, 0x64, 0x65, 0x6c, 0x61,
0x79, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73,
0x66, 0x75, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x73, 0x75, 0x63, 0x63, 0x65,
0x73, 0x73, 0x66, 0x75, 0x6c, 0x22, 0x21, 0x0a, 0x07, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74,
0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08,
0x52, 0x06, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x64, 0x22, 0x23, 0x0a, 0x0d, 0x55, 0x69, 0x6e, 0x74,
0x36, 0x34, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74,
0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x22, 0x0a,
0x0c, 0x49, 0x4f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a,
0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74,
0x61, 0x22, 0x89, 0x01, 0x0a, 0x05, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x12, 0x12, 0x0a, 0x04, 0x75,
0x73, 0x65, 0x36, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, 0x75, 0x73, 0x65, 0x36, 0x12,
0x19, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x50, 0x52, 0x02, 0x69, 0x70, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f,
0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2e, 0x0a,
0x13, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5f, 0x62, 0x6f, 0x6f, 0x74, 0x5f,
0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x64, 0x61, 0x73, 0x68,
0x62, 0x6f, 0x61, 0x72, 0x64, 0x42, 0x6f, 0x6f, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x2c, 0x0a,
0x02, 0x49, 0x50, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x34, 0x18, 0x01, 0x20, 0x01, 0x28,
0x09, 0x52, 0x04, 0x69, 0x70, 0x76, 0x34, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x36, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x70, 0x76, 0x36, 0x32, 0xd2, 0x02, 0x0a, 0x0c,
0x4e, 0x65, 0x7a, 0x68, 0x61, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x11,
0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x53, 0x74, 0x61, 0x74,
0x65, 0x12, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x1a,
0x03, 0x28, 0x01, 0x52, 0x03, 0x67, 0x70, 0x75, 0x12, 0x24, 0x0a, 0x04, 0x67, 0x70, 0x75, 0x73,
0x18, 0x12, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53,
0x74, 0x61, 0x74, 0x65, 0x5f, 0x47, 0x50, 0x55, 0x52, 0x04, 0x67, 0x70, 0x75, 0x73, 0x22, 0x71,
0x0a, 0x09, 0x53, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x47, 0x50, 0x55, 0x12, 0x20, 0x0a, 0x0b, 0x75,
0x74, 0x69, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01,
0x52, 0x0b, 0x75, 0x74, 0x69, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a,
0x0b, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01,
0x28, 0x04, 0x52, 0x0a, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x73, 0x65, 0x64, 0x12, 0x21,
0x0a, 0x0c, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x03,
0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x54, 0x6f, 0x74, 0x61,
0x6c, 0x22, 0x4f, 0x0a, 0x17, 0x53, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x53, 0x65, 0x6e, 0x73, 0x6f,
0x72, 0x54, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04,
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x12, 0x20, 0x0a, 0x0b, 0x74, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18,
0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0b, 0x74, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75,
0x72, 0x65, 0x22, 0x3e, 0x0a, 0x04, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79,
0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12,
0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61,
0x74, 0x61, 0x22, 0x7a, 0x0a, 0x0a, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74,
0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69, 0x64,
0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04,
0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x03, 0x20,
0x01, 0x28, 0x02, 0x52, 0x05, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61,
0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1e,
0x0a, 0x0a, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x18, 0x05, 0x20, 0x01,
0x28, 0x08, 0x52, 0x0a, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x66, 0x75, 0x6c, 0x22, 0x21,
0x0a, 0x07, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x6f,
0x63, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x70, 0x72, 0x6f, 0x63, 0x65,
0x64, 0x22, 0x23, 0x0a, 0x0d, 0x55, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x52, 0x65, 0x63, 0x65, 0x69,
0x70, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x22, 0x0a, 0x0c, 0x49, 0x4f, 0x53, 0x74, 0x72, 0x65,
0x61, 0x6d, 0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x89, 0x01, 0x0a, 0x05, 0x47,
0x65, 0x6f, 0x49, 0x50, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x36, 0x18, 0x01, 0x20, 0x01,
0x28, 0x08, 0x52, 0x04, 0x75, 0x73, 0x65, 0x36, 0x12, 0x19, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x50, 0x52,
0x02, 0x69, 0x70, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63,
0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74,
0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f,
0x61, 0x72, 0x64, 0x5f, 0x62, 0x6f, 0x6f, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20,
0x01, 0x28, 0x04, 0x52, 0x11, 0x64, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x42, 0x6f,
0x6f, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x2c, 0x0a, 0x02, 0x49, 0x50, 0x12, 0x12, 0x0a, 0x04,
0x69, 0x70, 0x76, 0x34, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x70, 0x76, 0x34,
0x12, 0x12, 0x0a, 0x04, 0x69, 0x70, 0x76, 0x36, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
0x69, 0x70, 0x76, 0x36, 0x32, 0xd2, 0x02, 0x0a, 0x0c, 0x4e, 0x65, 0x7a, 0x68, 0x61, 0x53, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x11, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53,
0x79, 0x73, 0x74, 0x65, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0c, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x1a, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x31,
0x0a, 0x10, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x49, 0x6e,
0x66, 0x6f, 0x12, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x1a,
0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x22,
0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x31, 0x0a, 0x10, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53,
0x79, 0x73, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52,
0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x0b, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x1a, 0x0b, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x3a, 0x0a,
0x08, 0x49, 0x4f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x2e, 0x49, 0x4f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x61, 0x74, 0x61, 0x1a, 0x13,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x4f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44,
0x61, 0x74, 0x61, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x2b, 0x0a, 0x0b, 0x52, 0x65, 0x70,
0x6f, 0x72, 0x74, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x12, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47,
0x65, 0x6f, 0x49, 0x50, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x11, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74,
0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x32, 0x12, 0x0b, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x2e, 0x55, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x22, 0x00,
0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
0x00, 0x12, 0x33, 0x0a, 0x0b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x61, 0x73, 0x6b,
0x12, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73,
0x75, 0x6c, 0x74, 0x1a, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x61, 0x73, 0x6b,
0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x3a, 0x0a, 0x08, 0x49, 0x4f, 0x53, 0x74, 0x72, 0x65,
0x61, 0x6d, 0x12, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x4f, 0x53, 0x74, 0x72,
0x65, 0x61, 0x6d, 0x44, 0x61, 0x74, 0x61, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
0x49, 0x4f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x44, 0x61, 0x74, 0x61, 0x22, 0x00, 0x28, 0x01,
0x30, 0x01, 0x12, 0x2b, 0x0a, 0x0b, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x47, 0x65, 0x6f, 0x49,
0x50, 0x12, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x1a,
0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x22, 0x00, 0x12,
0x38, 0x0a, 0x11, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x49,
0x6e, 0x66, 0x6f, 0x32, 0x12, 0x0b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x6f, 0x73,
0x74, 0x1a, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x69, 0x6e, 0x74, 0x36, 0x34,
0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x22, 0x00, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -918,39 +1001,41 @@ func file_proto_nezha_proto_rawDescGZIP() []byte {
return file_proto_nezha_proto_rawDescData
}
var file_proto_nezha_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
var file_proto_nezha_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
var file_proto_nezha_proto_goTypes = []any{
(*Host)(nil), // 0: proto.Host
(*State)(nil), // 1: proto.State
(*State_SensorTemperature)(nil), // 2: proto.State_SensorTemperature
(*Task)(nil), // 3: proto.Task
(*TaskResult)(nil), // 4: proto.TaskResult
(*Receipt)(nil), // 5: proto.Receipt
(*Uint64Receipt)(nil), // 6: proto.Uint64Receipt
(*IOStreamData)(nil), // 7: proto.IOStreamData
(*GeoIP)(nil), // 8: proto.GeoIP
(*IP)(nil), // 9: proto.IP
(*State_GPU)(nil), // 2: proto.State_GPU
(*State_SensorTemperature)(nil), // 3: proto.State_SensorTemperature
(*Task)(nil), // 4: proto.Task
(*TaskResult)(nil), // 5: proto.TaskResult
(*Receipt)(nil), // 6: proto.Receipt
(*Uint64Receipt)(nil), // 7: proto.Uint64Receipt
(*IOStreamData)(nil), // 8: proto.IOStreamData
(*GeoIP)(nil), // 9: proto.GeoIP
(*IP)(nil), // 10: proto.IP
}
var file_proto_nezha_proto_depIdxs = []int32{
2, // 0: proto.State.temperatures:type_name -> proto.State_SensorTemperature
9, // 1: proto.GeoIP.ip:type_name -> proto.IP
1, // 2: proto.NezhaService.ReportSystemState:input_type -> proto.State
0, // 3: proto.NezhaService.ReportSystemInfo:input_type -> proto.Host
4, // 4: proto.NezhaService.RequestTask:input_type -> proto.TaskResult
7, // 5: proto.NezhaService.IOStream:input_type -> proto.IOStreamData
8, // 6: proto.NezhaService.ReportGeoIP:input_type -> proto.GeoIP
0, // 7: proto.NezhaService.ReportSystemInfo2:input_type -> proto.Host
5, // 8: proto.NezhaService.ReportSystemState:output_type -> proto.Receipt
5, // 9: proto.NezhaService.ReportSystemInfo:output_type -> proto.Receipt
3, // 10: proto.NezhaService.RequestTask:output_type -> proto.Task
7, // 11: proto.NezhaService.IOStream:output_type -> proto.IOStreamData
8, // 12: proto.NezhaService.ReportGeoIP:output_type -> proto.GeoIP
6, // 13: proto.NezhaService.ReportSystemInfo2:output_type -> proto.Uint64Receipt
8, // [8:14] is the sub-list for method output_type
2, // [2:8] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
3, // 0: proto.State.temperatures:type_name -> proto.State_SensorTemperature
2, // 1: proto.State.gpus:type_name -> proto.State_GPU
10, // 2: proto.GeoIP.ip:type_name -> proto.IP
1, // 3: proto.NezhaService.ReportSystemState:input_type -> proto.State
0, // 4: proto.NezhaService.ReportSystemInfo:input_type -> proto.Host
5, // 5: proto.NezhaService.RequestTask:input_type -> proto.TaskResult
8, // 6: proto.NezhaService.IOStream:input_type -> proto.IOStreamData
9, // 7: proto.NezhaService.ReportGeoIP:input_type -> proto.GeoIP
0, // 8: proto.NezhaService.ReportSystemInfo2:input_type -> proto.Host
6, // 9: proto.NezhaService.ReportSystemState:output_type -> proto.Receipt
6, // 10: proto.NezhaService.ReportSystemInfo:output_type -> proto.Receipt
4, // 11: proto.NezhaService.RequestTask:output_type -> proto.Task
8, // 12: proto.NezhaService.IOStream:output_type -> proto.IOStreamData
9, // 13: proto.NezhaService.ReportGeoIP:output_type -> proto.GeoIP
7, // 14: proto.NezhaService.ReportSystemInfo2:output_type -> proto.Uint64Receipt
9, // [9:15] is the sub-list for method output_type
3, // [3:9] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
}
func init() { file_proto_nezha_proto_init() }
@@ -984,7 +1069,7 @@ func file_proto_nezha_proto_init() {
}
}
file_proto_nezha_proto_msgTypes[2].Exporter = func(v any, i int) any {
switch v := v.(*State_SensorTemperature); i {
switch v := v.(*State_GPU); i {
case 0:
return &v.state
case 1:
@@ -996,7 +1081,7 @@ func file_proto_nezha_proto_init() {
}
}
file_proto_nezha_proto_msgTypes[3].Exporter = func(v any, i int) any {
switch v := v.(*Task); i {
switch v := v.(*State_SensorTemperature); i {
case 0:
return &v.state
case 1:
@@ -1008,7 +1093,7 @@ func file_proto_nezha_proto_init() {
}
}
file_proto_nezha_proto_msgTypes[4].Exporter = func(v any, i int) any {
switch v := v.(*TaskResult); i {
switch v := v.(*Task); i {
case 0:
return &v.state
case 1:
@@ -1020,7 +1105,7 @@ func file_proto_nezha_proto_init() {
}
}
file_proto_nezha_proto_msgTypes[5].Exporter = func(v any, i int) any {
switch v := v.(*Receipt); i {
switch v := v.(*TaskResult); i {
case 0:
return &v.state
case 1:
@@ -1032,7 +1117,7 @@ func file_proto_nezha_proto_init() {
}
}
file_proto_nezha_proto_msgTypes[6].Exporter = func(v any, i int) any {
switch v := v.(*Uint64Receipt); i {
switch v := v.(*Receipt); i {
case 0:
return &v.state
case 1:
@@ -1044,7 +1129,7 @@ func file_proto_nezha_proto_init() {
}
}
file_proto_nezha_proto_msgTypes[7].Exporter = func(v any, i int) any {
switch v := v.(*IOStreamData); i {
switch v := v.(*Uint64Receipt); i {
case 0:
return &v.state
case 1:
@@ -1056,7 +1141,7 @@ func file_proto_nezha_proto_init() {
}
}
file_proto_nezha_proto_msgTypes[8].Exporter = func(v any, i int) any {
switch v := v.(*GeoIP); i {
switch v := v.(*IOStreamData); i {
case 0:
return &v.state
case 1:
@@ -1068,6 +1153,18 @@ func file_proto_nezha_proto_init() {
}
}
file_proto_nezha_proto_msgTypes[9].Exporter = func(v any, i int) any {
switch v := v.(*GeoIP); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proto_nezha_proto_msgTypes[10].Exporter = func(v any, i int) any {
switch v := v.(*IP); i {
case 0:
return &v.state
@@ -1086,7 +1183,7 @@ func file_proto_nezha_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_proto_nezha_proto_rawDesc,
NumEnums: 0,
NumMessages: 10,
NumMessages: 11,
NumExtensions: 0,
NumServices: 1,
},
+10
View File
@@ -44,6 +44,16 @@ message State {
uint64 process_count = 15;
repeated State_SensorTemperature temperatures = 16;
repeated double gpu = 17;
repeated State_GPU gpus = 18;
}
// State_GPU carries per-card figures. Index-aligned with Host.gpu, matching
// the existing `gpu` field it supersedes; that field stays populated so older
// dashboards keep working.
message State_GPU {
double utilization = 1;
uint64 memory_used = 2;
uint64 memory_total = 3;
}
message State_SensorTemperature {
+1 -1
View File
@@ -2,7 +2,7 @@
name: "OfficialAdmin"
repository: "https://github.com/nezhahq/admin-frontend"
author: "nezhahq"
version: "v2.3.5"
version: "v2.3.6"
is_admin: true
is_official: true
- path: "user-dist"
+15 -2
View File
@@ -1,6 +1,7 @@
package singleton
import (
"errors"
"log"
"time"
@@ -10,6 +11,8 @@ import (
var TSDBShared *tsdb.TSDB
var openTSDB = tsdb.Open
func InitTSDB() error {
config := &tsdb.Config{
RetentionDays: 30,
@@ -44,11 +47,21 @@ func InitTSDB() error {
return nil
}
var err error
TSDBShared, err = tsdb.Open(config)
TSDBShared = nil
db, err := openTSDB(config)
if err != nil {
if errors.Is(err, tsdb.ErrDiskFull) {
log.Printf("NEZHA>> Warning: TSDB is unavailable because its disk is full; dashboard and alerts will continue without TSDB writes: %v", err)
if DB != nil {
if migrateErr := DB.AutoMigrate(model.ServiceHistory{}); migrateErr != nil {
log.Printf("NEZHA>> Warning: failed to prepare SQLite service history fallback: %v", migrateErr)
}
}
return nil
}
return err
}
TSDBShared = db
log.Println("NEZHA>> TSDB initialized successfully")
+58
View File
@@ -0,0 +1,58 @@
package singleton
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
"github.com/nezhahq/nezha/model"
"github.com/nezhahq/nezha/pkg/tsdb"
)
func TestInitTSDBContinuesWhenDiskIsFull(t *testing.T) {
originalConf := Conf
originalDB := DB
originalShared := TSDBShared
originalOpen := openTSDB
t.Cleanup(func() {
Conf = originalConf
DB = originalDB
TSDBShared = originalShared
openTSDB = originalOpen
})
Conf = &ConfigClass{Config: &model.Config{
TSDB: model.TSDBConf{DataPath: "/full/tsdb"},
}}
DB = nil
TSDBShared = nil
openTSDB = func(*tsdb.Config) (*tsdb.TSDB, error) {
return nil, fmt.Errorf("open failed: %w", tsdb.ErrDiskFull)
}
require.NoError(t, InitTSDB())
require.Nil(t, TSDBShared)
}
func TestInitTSDBStillReturnsNonDiskErrors(t *testing.T) {
originalConf := Conf
originalShared := TSDBShared
originalOpen := openTSDB
t.Cleanup(func() {
Conf = originalConf
TSDBShared = originalShared
openTSDB = originalOpen
})
Conf = &ConfigClass{Config: &model.Config{
TSDB: model.TSDBConf{DataPath: "/broken/tsdb"},
}}
TSDBShared = nil
openTSDB = func(*tsdb.Config) (*tsdb.TSDB, error) {
return nil, fmt.Errorf("permission denied")
}
require.ErrorContains(t, InitTSDB(), "permission denied")
require.Nil(t, TSDBShared)
}