Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions jdm-core/src/main/java/jdiskmark/Smart.java
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,12 @@ public static void logSmart(Smart smart) {
@JsonProperty("local_time")
private LocalTimeInfo localTime;

@JsonProperty("rotation_rate")
private Integer rotationRate;

@JsonProperty("in_smartctl_database")
private Boolean inSmartctlDatabase;

// -------------------------------------------------------------------------
// Factory / parsing
// -------------------------------------------------------------------------
Expand Down Expand Up @@ -697,6 +703,12 @@ public static Smart fromJson(String json) throws IOException {
public LocalTimeInfo getLocalTime() { return localTime; }
public void setLocalTime(LocalTimeInfo localTime) { this.localTime = localTime; }

public Integer getRotationRate() { return rotationRate; }
public void setRotationRate(Integer rotationRate) { this.rotationRate = rotationRate; }

public Boolean getInSmartctlDatabase() { return inSmartctlDatabase; }
public void setInSmartctlDatabase(Boolean inSmartctlDatabase) { this.inSmartctlDatabase = inSmartctlDatabase; }

// =========================================================================
// Nested classes
// =========================================================================
Expand Down
161 changes: 152 additions & 9 deletions jdm-core/src/main/java/jdiskmark/SmartPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,13 @@ public class SmartPanel extends JPanel {
// Drive Info labels
// -------------------------------------------------------------------------
private final JLabel modelValueLabel = value("-");
private final JLabel modelFamilyValueLabel = value("-");
private final JLabel serialValueLabel = value("-");
private final JLabel firmwareValueLabel = value("-");
private final JLabel capacityValueLabel = value("-");
private final JLabel protocolValueLabel = value("-");
private final JLabel driveTypeValueLabel = value("-");
private final JLabel inDatabaseValueLabel = value("-");

// -------------------------------------------------------------------------
// NVMe Device Details labels
Expand All @@ -68,6 +71,7 @@ public class SmartPanel extends JPanel {
// -------------------------------------------------------------------------
private final JLabel statusValueLabel = value("-");
private final JLabel tempValueLabel = value("-");
private final JLabel tempTripValueLabel = value("-");
private final JLabel powerOnValueLabel = value("-");
private final JLabel powerCyclesValueLabel = value("-");
private final JLabel remainingLifeValueLabel = value("-");
Expand Down Expand Up @@ -112,6 +116,13 @@ public class SmartPanel extends JPanel {
private final JTable ataTable;
private JPanel ataSection;

// -------------------------------------------------------------------------
// NVMe Attributes table
// -------------------------------------------------------------------------
private final DefaultTableModel nvmeAttrModel;
private final JTable nvmeAttrTable;
private JPanel nvmeAttrSection;

// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
Expand All @@ -136,6 +147,19 @@ public SmartPanel() {
ataTable.getColumnModel().getColumn(5).setPreferredWidth(80);
ataTable.getColumnModel().getColumn(6).setPreferredWidth(60);

nvmeAttrModel = new DefaultTableModel(
new String[]{"Attribute Name", "Value", "Status"},
0
) {
@Override
public boolean isCellEditable(int row, int col) { return false; }
};
nvmeAttrTable = new JTable(nvmeAttrModel);
nvmeAttrTable.setFillsViewportHeight(true);
nvmeAttrTable.getColumnModel().getColumn(0).setPreferredWidth(220);
nvmeAttrTable.getColumnModel().getColumn(1).setPreferredWidth(200);
nvmeAttrTable.getColumnModel().getColumn(2).setPreferredWidth(60);

// Inner content panel — implements Scrollable so the scroll pane uses
// the panel's natural preferred height instead of stretching to fill the viewport.
ContentPanel contentPanel = new ContentPanel();
Expand Down Expand Up @@ -190,15 +214,21 @@ private void buildLayout(JPanel p) {
JPanel driveSection = section("Drive Info");
driveSection.setLayout(new MigLayout("insets 8, wrap 4", COL_SPEC));
driveSection.add(label("Model:"));
driveSection.add(modelValueLabel, "growx");
driveSection.add(modelValueLabel, "growx");
driveSection.add(label("Protocol:"));
driveSection.add(protocolValueLabel, "growx");
driveSection.add(protocolValueLabel, "growx");
driveSection.add(label("Model Family:"));
driveSection.add(modelFamilyValueLabel, "growx");
driveSection.add(label("Drive Type:"));
driveSection.add(driveTypeValueLabel, "growx");
driveSection.add(label("Serial:"));
driveSection.add(serialValueLabel, "growx");
driveSection.add(serialValueLabel, "growx");
driveSection.add(label("Firmware:"));
driveSection.add(firmwareValueLabel, "growx");
driveSection.add(firmwareValueLabel, "growx");
driveSection.add(label("Capacity:"));
driveSection.add(capacityValueLabel, "growx, span 3");
driveSection.add(capacityValueLabel, "growx");
driveSection.add(label("In SMART DB:"));
driveSection.add(inDatabaseValueLabel, "growx");
p.add(driveSection, "growx, wrap");

// --- NVMe Device Details section ---
Expand Down Expand Up @@ -234,6 +264,8 @@ private void buildLayout(JPanel p) {
healthSection.add(tempValueLabel, "growx");
healthSection.add(label("Power-On Hours:"));
healthSection.add(powerOnValueLabel, "growx");
healthSection.add(label("Trip Temperature:"));
healthSection.add(tempTripValueLabel, "growx");
healthSection.add(label("Power Cycles:"));
healthSection.add(powerCyclesValueLabel, "growx");
healthSection.add(label("Remaining Life:"));
Expand Down Expand Up @@ -292,6 +324,15 @@ private void buildLayout(JPanel p) {
ataSection.add(ataScroll, "grow");
ataSection.setVisible(false);
p.add(ataSection, "growx, wrap");

// --- NVMe Attributes section (hidden until NVMe data is present) ---
nvmeAttrSection = section("NVMe SMART Attributes");
nvmeAttrSection.setLayout(new MigLayout("insets 8, fill", "[grow]", "[grow]"));
JScrollPane nvmeAttrScroll = new JScrollPane(nvmeAttrTable);
nvmeAttrScroll.setPreferredSize(new java.awt.Dimension(600, 200));
nvmeAttrSection.add(nvmeAttrScroll, "grow");
nvmeAttrSection.setVisible(false);
p.add(nvmeAttrSection, "growx, wrap");
}

// -------------------------------------------------------------------------
Expand All @@ -316,6 +357,7 @@ public void populate(Smart data) {
fillNvme(data.getNvmeHealthLog());
fillEndurance(data);
fillAtaAttributes(data.getAtaSmartAttributes());
fillNvmeAttributes(data.getNvmeHealthLog());
revalidate();
repaint();
});
Expand All @@ -325,13 +367,15 @@ public void populate(Smart data) {
public void clear() {
SwingUtilities.invokeLater(() -> {
for (JLabel l : new JLabel[]{
modelValueLabel, serialValueLabel, firmwareValueLabel,
modelValueLabel, modelFamilyValueLabel,
serialValueLabel, firmwareValueLabel,
capacityValueLabel, protocolValueLabel,
driveTypeValueLabel, inDatabaseValueLabel,
nvmeVersionValueLabel, nvmeControllerIdValueLabel,
nvmeOuiValueLabel, nvmeVendorValueLabel,
nvmeTotalCapValueLabel, nvmeUnallocCapValueLabel,
nvmeNsCountValueLabel, localTimeValueLabel,
statusValueLabel, tempValueLabel,
statusValueLabel, tempValueLabel, tempTripValueLabel,
powerOnValueLabel, powerCyclesValueLabel,
remainingLifeValueLabel,
spareValueLabel, usedPctValueLabel, writtenValueLabel,
Expand All @@ -346,10 +390,12 @@ public void clear() {
l.setForeground(null);
}
ataModel.setRowCount(0);
nvmeAttrModel.setRowCount(0);
nvmeDevSection.setVisible(true); // keep visible with dashes
nvmeSection.setVisible(true);
enduranceSection.setVisible(false);
ataSection.setVisible(false);
nvmeAttrSection.setVisible(false);
// Reset toolbar state
saveButton.setEnabled(false);
statusLabel.setText("Click \u2018Run SMART\u2019 to fetch live data.");
Expand Down Expand Up @@ -432,13 +478,15 @@ public void populateFromSnapshot(SmartSnapshot snap) {
SwingUtilities.invokeLater(() -> {
// Reset all value labels to dash
for (JLabel l : new JLabel[]{
modelValueLabel, serialValueLabel, firmwareValueLabel,
modelValueLabel, modelFamilyValueLabel,
serialValueLabel, firmwareValueLabel,
capacityValueLabel, protocolValueLabel,
driveTypeValueLabel, inDatabaseValueLabel,
nvmeVersionValueLabel, nvmeControllerIdValueLabel,
nvmeOuiValueLabel, nvmeVendorValueLabel,
nvmeTotalCapValueLabel, nvmeUnallocCapValueLabel,
nvmeNsCountValueLabel, localTimeValueLabel,
statusValueLabel, tempValueLabel,
statusValueLabel, tempValueLabel, tempTripValueLabel,
powerOnValueLabel, powerCyclesValueLabel,
remainingLifeValueLabel,
spareValueLabel, usedPctValueLabel, writtenValueLabel,
Expand All @@ -450,6 +498,7 @@ public void populateFromSnapshot(SmartSnapshot snap) {
eccErrorValueLabel, uncorrErrorValueLabel
}) { l.setText("-"); l.setForeground(null); }
ataModel.setRowCount(0);
nvmeAttrModel.setRowCount(0);

// Drive Info
modelValueLabel.setText(orDash(snap.getModelName()));
Expand Down Expand Up @@ -511,6 +560,7 @@ public void populateFromSnapshot(SmartSnapshot snap) {
nvmeDevSection.setVisible(false);
enduranceSection.setVisible(false);
ataSection.setVisible(false);
nvmeAttrSection.setVisible(false);

// Toolbar: disable Save (this is a read-only view), show snapshot timestamp
saveButton.setEnabled(false);
Expand All @@ -532,6 +582,7 @@ public void populateFromSnapshot(SmartSnapshot snap) {

private void fillDriveInfo(Smart d) {
modelValueLabel.setText(orDash(d.getModelName()));
modelFamilyValueLabel.setText(orDash(d.getModelFamily()));
serialValueLabel.setText(orDash(d.getSerialNumber()));
firmwareValueLabel.setText(orDash(d.getFirmwareVersion()));

Expand All @@ -541,6 +592,19 @@ private void fillDriveInfo(Smart d) {
if (d.getDevice() != null) {
protocolValueLabel.setText(orDash(d.getDevice().getProtocol()));
}

// rotation_rate: 0 = SSD, >0 = HDD with that RPM, null = unknown
if (d.getRotationRate() != null) {
int rpm = d.getRotationRate();
driveTypeValueLabel.setText(rpm == 0 ? "SSD (non-rotating)" : "HDD (" + rpm + " RPM)");
}
Comment on lines +596 to +600

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JasmineRRod this sounds like a good suggestion to me


// in_smartctl_database: whether attribute names/thresholds are reliable
if (d.getInSmartctlDatabase() != null) {
boolean inDb = d.getInSmartctlDatabase();
inDatabaseValueLabel.setText(inDb ? "Yes ✔" : "No — thresholds may be unreliable");
inDatabaseValueLabel.setForeground(inDb ? null : new Color(0xFF9800));
}
}

private void fillNvmeDevDetails(Smart d) {
Expand Down Expand Up @@ -604,6 +668,10 @@ private void fillHealth(Smart d) {
int t = d.getTemperature().getCurrent();
tempValueLabel.setText(t + " °C");
tempValueLabel.setForeground(tempColor(t));
// Drive trip temperature (ATA only)
if (d.getTemperature().getDriveTrip() != null) {
tempTripValueLabel.setText(d.getTemperature().getDriveTrip() + " °C");
}
} else if (d.getNvmeHealthLog() != null && d.getNvmeHealthLog().getTemperature() != null) {
int t = d.getNvmeHealthLog().getTemperature();
tempValueLabel.setText(t + " °C");
Expand Down Expand Up @@ -751,6 +819,81 @@ private void fillAtaAttributes(Smart.AtaSmartAttributes ata) {
}
}

private void fillNvmeAttributes(Smart.NvmeHealthLog nvme) {
nvmeAttrModel.setRowCount(0);
if (nvme == null) {
nvmeAttrSection.setVisible(false);
return;
}
nvmeAttrSection.setVisible(true);

addNvmeRow("Temperature",
nvme.getTemperature() != null ? nvme.getTemperature() + " \u00b0C" : null,
nvme.getTemperature() != null && nvme.getTemperature() >= 60 ? "WARN" : "OK");
addNvmeRow("Available Spare",
nvme.getAvailableSpare() != null ? nvme.getAvailableSpare() + "%" : null,
nvme.getAvailableSpareThreshold() != null && nvme.getAvailableSpare() != null
&& nvme.getAvailableSpare() <= nvme.getAvailableSpareThreshold() ? "WARN" : "OK");
addNvmeRow("Available Spare Threshold",
nvme.getAvailableSpareThreshold() != null ? nvme.getAvailableSpareThreshold() + "%" : null,
"OK");
addNvmeRow("Percentage Used",
nvme.getPercentageUsed() != null ? nvme.getPercentageUsed() + "%" : null,
nvme.getPercentageUsed() != null && nvme.getPercentageUsed() >= 90 ? "WARN" : "OK");
addNvmeRow("Data Units Written",
nvme.getDataUnitsWritten() != null
? nvme.getDataWrittenGb() + " GB (" + nvme.getDataUnitsWritten() + " units)" : null,
"OK");
addNvmeRow("Data Units Read",
nvme.getDataUnitsRead() != null
? nvme.getDataReadGb() + " GB (" + nvme.getDataUnitsRead() + " units)" : null,
"OK");
addNvmeRow("Host Write Commands",
nvme.getHostWrites() != null ? String.valueOf(nvme.getHostWrites()) : null,
"OK");
addNvmeRow("Host Read Commands",
nvme.getHostReads() != null ? String.valueOf(nvme.getHostReads()) : null,
"OK");
addNvmeRow("Controller Busy Time",
nvme.getControllerBusyTime() != null ? nvme.getControllerBusyTime() + " min" : null,
"OK");
addNvmeRow("Power Cycles",
nvme.getPowerCycles() != null ? String.valueOf(nvme.getPowerCycles()) : null,
"OK");
addNvmeRow("Power On Hours",
nvme.getPowerOnHours() != null ? nvme.getPowerOnHours() + " h" : null,
"OK");
addNvmeRow("Unsafe Shutdowns",
nvme.getUnsafeShutdowns() != null ? String.valueOf(nvme.getUnsafeShutdowns()) : null,
nvme.getUnsafeShutdowns() != null && nvme.getUnsafeShutdowns() > 0 ? "WARN" : "OK");
addNvmeRow("Media and Data Integrity Errors",
nvme.getMediaErrors() != null ? String.valueOf(nvme.getMediaErrors()) : null,
nvme.getMediaErrors() != null && nvme.getMediaErrors() > 0 ? "WARN" : "OK");
addNvmeRow("Error Information Log Entries",
nvme.getNumErrLogEntries() != null ? String.valueOf(nvme.getNumErrLogEntries()) : null,
nvme.getNumErrLogEntries() != null && nvme.getNumErrLogEntries() > 0 ? "WARN" : "OK");
addNvmeRow("Warning Composite Temp Time",
nvme.getWarningTempTime() != null ? nvme.getWarningTempTime() + " min" : null,
nvme.getWarningTempTime() != null && nvme.getWarningTempTime() > 0 ? "WARN" : "OK");
addNvmeRow("Critical Composite Temp Time",
nvme.getCriticalCompTime() != null ? nvme.getCriticalCompTime() + " min" : null,
nvme.getCriticalCompTime() != null && nvme.getCriticalCompTime() > 0 ? "WARN" : "OK");
List<Integer> sensors = nvme.getTemperatureSensors();
if (sensors != null) {
for (int i = 0; i < sensors.size(); i++) {
Integer s = sensors.get(i);
addNvmeRow("Temperature Sensor " + (i + 1),
s != null ? s + " \u00b0C" : null,
s != null && s >= 60 ? "WARN" : "OK");
}
}
}

private void addNvmeRow(String name, String value, String status) {
if (value == null) return;
nvmeAttrModel.addRow(new Object[]{name, value, status});
}

// -------------------------------------------------------------------------
// Small helpers
// -------------------------------------------------------------------------
Expand Down
Loading