Add option to discard SCT Data Table Temperature History (#557)

Fixes #494
This commit is contained in:
mcarbonne
2026-02-06 05:59:24 +01:00
committed by GitHub
parent 761014a93f
commit bdbe13e320
11 changed files with 67 additions and 13 deletions
+1 -1
View File
@@ -163,7 +163,7 @@ docker cp scrutiny:/tmp/web.log web.log
# Docker Development
```
docker build -f docker/Dockerfile . -t chcr.io/analogj/scrutiny:master-omnibus
docker build -f docker/Dockerfile . -t ghcr.io/analogj/scrutiny:master-omnibus
docker run -it --rm -p 8080:8080 \
-v /run/udev:/run/udev:ro \
--cap-add SYS_RAWIO \
+1 -1
View File
@@ -26,7 +26,7 @@ type DeviceRepo interface {
SaveSmartAttributes(ctx context.Context, wwn string, collectorSmartData collector.SmartInfo) (measurements.Smart, error)
GetSmartAttributeHistory(ctx context.Context, wwn string, durationKey string, selectEntries int, selectEntriesOffset int, attributes []string) ([]measurements.Smart, error)
SaveSmartTemperature(ctx context.Context, wwn string, deviceProtocol string, collectorSmartData collector.SmartInfo) error
SaveSmartTemperature(ctx context.Context, wwn string, deviceProtocol string, collectorSmartData collector.SmartInfo, discardSCTTempHistory bool) error
GetSummary(ctx context.Context) (map[string]*models.DeviceSummary, error)
GetSmartTemperatureHistory(ctx context.Context, durationKey string) (map[string][]measurements.SmartTemperature, error)
@@ -228,17 +228,17 @@ func (mr *MockDeviceRepoMockRecorder) SaveSmartAttributes(ctx, wwn, collectorSma
}
// SaveSmartTemperature mocks base method.
func (m *MockDeviceRepo) SaveSmartTemperature(ctx context.Context, wwn, deviceProtocol string, collectorSmartData collector.SmartInfo) error {
func (m *MockDeviceRepo) SaveSmartTemperature(ctx context.Context, wwn, deviceProtocol string, collectorSmartData collector.SmartInfo, discardSCTTempHistory bool) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SaveSmartTemperature", ctx, wwn, deviceProtocol, collectorSmartData)
ret := m.ctrl.Call(m, "SaveSmartTemperature", ctx, wwn, deviceProtocol, collectorSmartData, discardSCTTempHistory)
ret0, _ := ret[0].(error)
return ret0
}
// SaveSmartTemperature indicates an expected call of SaveSmartTemperature.
func (mr *MockDeviceRepoMockRecorder) SaveSmartTemperature(ctx, wwn, deviceProtocol, collectorSmartData interface{}) *gomock.Call {
func (mr *MockDeviceRepoMockRecorder) SaveSmartTemperature(ctx, wwn, deviceProtocol, collectorSmartData, discardSCTTempHistory interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveSmartTemperature", reflect.TypeOf((*MockDeviceRepo)(nil).SaveSmartTemperature), ctx, wwn, deviceProtocol, collectorSmartData)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveSmartTemperature", reflect.TypeOf((*MockDeviceRepo)(nil).SaveSmartTemperature), ctx, wwn, deviceProtocol, collectorSmartData, discardSCTTempHistory)
}
// UpdateDevice mocks base method.
@@ -409,6 +409,21 @@ func (sr *scrutinyRepository) Migrate(ctx context.Context) error {
return tx.AutoMigrate(m20250221084400.Device{})
},
},
{
ID: "m20260105083200", // add discard_sct_temp_history setting.
Migrate: func(tx *gorm.DB) error {
//add discard_sct_temp_history setting default.
var defaultSettings = []m20220716214900.Setting{
{
SettingKeyName: "collector.discard_sct_temp_history",
SettingKeyDescription: "Whether to discard SCT Temperature history (true | false)",
SettingDataType: "bool",
SettingValueBool: false,
},
}
return tx.Create(&defaultSettings).Error
},
},
})
if err := m.Migrate(); err != nil {
@@ -3,18 +3,19 @@ package database
import (
"context"
"fmt"
"strings"
"time"
"github.com/analogj/scrutiny/webapp/backend/pkg/models/collector"
"github.com/analogj/scrutiny/webapp/backend/pkg/models/measurements"
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
"strings"
"time"
)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Temperature Data
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func (sr *scrutinyRepository) SaveSmartTemperature(ctx context.Context, wwn string, deviceProtocol string, collectorSmartData collector.SmartInfo) error {
if len(collectorSmartData.AtaSctTemperatureHistory.Table) > 0 {
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
func (sr *scrutinyRepository) SaveSmartTemperature(ctx context.Context, wwn string, deviceProtocol string, collectorSmartData collector.SmartInfo, discardSCTTempHistory bool) error {
if len(collectorSmartData.AtaSctTemperatureHistory.Table) > 0 && !discardSCTTempHistory {
for ndx, temp := range collectorSmartData.AtaSctTemperatureHistory.Table {
//temp value may be null, we must skip/ignore them. See #393
+4
View File
@@ -17,6 +17,10 @@ type Settings struct {
LineStroke string `json:"line_stroke" mapstructure:"line_stroke"`
PoweredOnHoursUnit string `json:"powered_on_hours_unit" mapstructure:"powered_on_hours_unit"`
Collector struct {
DiscardSCTTempHistory bool `json:"discard_sct_temp_history" mapstructure:"discard_sct_temp_history"`
} `json:"collector" mapstructure:"collector"`
Metrics struct {
NotifyLevel int `json:"notify_level" mapstructure:"notify_level"`
StatusFilterAttributes int `json:"status_filter_attributes" mapstructure:"status_filter_attributes"`
@@ -61,7 +61,7 @@ func UploadDeviceMetrics(c *gin.Context) {
}
// save smart temperature data (ignore failures)
err = deviceRepo.SaveSmartTemperature(c, c.Param("wwn"), updatedDevice.DeviceProtocol, collectorSmartData)
err = deviceRepo.SaveSmartTemperature(c, c.Param("wwn"), updatedDevice.DeviceProtocol, collectorSmartData, appConfig.GetBool(fmt.Sprintf("%s.collector.discard_sct_temp_history", config.DB_USER_SETTINGS_SUBKEY)))
if err != nil {
logger.Errorln("An error occurred while saving smartctl temp data", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
+3
View File
@@ -191,6 +191,7 @@ func (suite *ServerTestSuite) TestUploadDeviceMetricsRoute() {
fakeConfig.EXPECT().GetString("web.influxdb.org").Return("scrutiny").AnyTimes()
fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes()
fakeConfig.EXPECT().GetBool("user.metrics.repeat_notifications").Return(true).AnyTimes()
fakeConfig.EXPECT().GetBool("user.collector.discard_sct_temp_history").Return(false).AnyTimes()
fakeConfig.EXPECT().GetBool("web.influxdb.tls.insecure_skip_verify").Return(false).AnyTimes()
fakeConfig.EXPECT().GetBool("web.influxdb.retention_policy").Return(false).AnyTimes()
if _, isGithubActions := os.LookupEnv("GITHUB_ACTIONS"); isGithubActions {
@@ -250,6 +251,7 @@ func (suite *ServerTestSuite) TestPopulateMultiple() {
fakeConfig.EXPECT().GetString("web.influxdb.org").Return("scrutiny").AnyTimes()
fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes()
fakeConfig.EXPECT().GetBool("user.metrics.repeat_notifications").Return(true).AnyTimes()
fakeConfig.EXPECT().GetBool("user.collector.discard_sct_temp_history").Return(false).AnyTimes()
fakeConfig.EXPECT().GetBool("web.influxdb.tls.insecure_skip_verify").Return(false).AnyTimes()
fakeConfig.EXPECT().GetBool("web.influxdb.retention_policy").Return(false).AnyTimes()
if _, isGithubActions := os.LookupEnv("GITHUB_ACTIONS"); isGithubActions {
@@ -533,6 +535,7 @@ func (suite *ServerTestSuite) TestGetDevicesSummaryRoute_Nvme() {
fakeConfig.EXPECT().GetString("web.influxdb.org").Return("scrutiny").AnyTimes()
fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes()
fakeConfig.EXPECT().GetBool("user.metrics.repeat_notifications").Return(true).AnyTimes()
fakeConfig.EXPECT().GetBool("user.collector.discard_sct_temp_history").Return(false).AnyTimes()
fakeConfig.EXPECT().GetBool("web.influxdb.tls.insecure_skip_verify").Return(false).AnyTimes()
fakeConfig.EXPECT().GetBool("web.influxdb.retention_policy").Return(false).AnyTimes()
fakeConfig.EXPECT().GetStringSlice("notify.urls").AnyTimes().Return([]string{})
@@ -55,6 +55,10 @@ export interface AppConfig {
// Settings from Scrutiny API
collector?: {
discard_sct_temp_history?: boolean
}
metrics?: {
notify_level?: MetricsNotifyLevel
status_filter_attributes?: MetricsStatusFilterAttributes
@@ -85,6 +89,10 @@ export const appConfig: AppConfig = {
line_stroke: 'smooth',
collector: {
discard_sct_temp_history : false,
},
metrics: {
notify_level: MetricsNotifyLevel.Fail,
status_filter_attributes: MetricsStatusFilterAttributes.All,
@@ -102,6 +102,23 @@
</mat-select>
</mat-form-field>
</div>
<div class="mt-6 mb-2">
<h3 class="text-lg font-medium">Quirks</h3>
<div class="w-full border-b mt-1"></div>
</div>
<div class="flex flex-col mt-5 gt-md:flex-row">
<mat-form-field class="flex-auto gt-xs:pr-3 gt-md:pr-3"
matTooltip="Discard historical temperature data retrieved from the SMART Command Transport (SCT) Data Table, which may be inaccurate for some drives. The current temperature is always stored."
matTooltipPosition="right">
<mat-label>Discard SCT Temperature History</mat-label>
<mat-select [(ngModel)]=discardSCTTempHistory>
<mat-option [value]=true>Enabled</mat-option>
<mat-option [value]=false>Disabled</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
</mat-dialog-content>
@@ -28,6 +28,7 @@ export class DashboardSettingsComponent implements OnInit {
poweredOnHoursUnit: string;
lineStroke: string;
theme: string;
discardSCTTempHistory: boolean;
statusThreshold: number;
statusFilterAttributes: number;
repeatNotifications: boolean;
@@ -57,6 +58,8 @@ export class DashboardSettingsComponent implements OnInit {
this.lineStroke = config.line_stroke;
this.theme = config.theme;
this.discardSCTTempHistory = config.collector.discard_sct_temp_history;
this.statusFilterAttributes = config.metrics.status_filter_attributes;
this.statusThreshold = config.metrics.status_threshold;
this.repeatNotifications = config.metrics.repeat_notifications;
@@ -74,6 +77,9 @@ export class DashboardSettingsComponent implements OnInit {
powered_on_hours_unit: this.poweredOnHoursUnit as DevicePoweredOnUnit,
line_stroke: this.lineStroke as LineStroke,
theme: this.theme as Theme,
collector: {
discard_sct_temp_history: this.discardSCTTempHistory
},
metrics: {
status_filter_attributes: this.statusFilterAttributes as MetricsStatusFilterAttributes,
status_threshold: this.statusThreshold as MetricsStatusThreshold,