[dbohdan] 2015-03-14: `ssd-info` is meant to be a lightweight [Linux] replacement for the Windows utility [http://ssd-life.com/eng/screenshots.html%|%SSD Life]. It reports the remaining write resource of your computer's solid state drive using either a [Tk] GUI or plain text. `ssd-info` requires [http://www.smartmontools.org/%|%smartctl] to be installed and must be run as root.
**Linux usage**
`sudo tclsh ssd-info.tcl /dev/sdX \[--no-gui]`
**Installation**
***Requirements***
You will need Tcl 8.56 or later, Tk and `smartctl` installed to run `ssd-info`. All are likely to be available to install from your Linux distribution's package repositories.
***Installing with [wiki-reaper]***
`wiki-reaper -x 41244 0 | tee ssd-info.tcl && chmod +x ssd-info.tcl`
or
`wiki-reaper -x 41244 0 | sudo tee /usr/local/bin/ssd-info && sudo chmod +x /usr/local/bin/ssd-info`
'''Warning:''' Make sure to review the code before running it as root.
**Screenshot**
[ssd-info-0.1-screenshot]
**Code**
======
#!/usr/bin/env tclsh
# ssd-info, a utility that reports the remaining write resource of an SSD.# Copyright (c) 2015, -2016, 2020-2021 D. Bohdan.
# License: MIT.
package require Tcl 8.6-10
namespace eval ssd-info { variable version 0.23.0
variable message {%1$s (%3$s) has %2$s%% of its write resource remaining.}
}
# Get the value of the SMART vendor attribute $name for $device.
proc ::ssd-info::get-attribute {device name} {
set lines [split [exec smartctl -A $device] \n]
set line [lsearch -inline -glob $lines *$name*]
set value [string trimleft [lindex $line 3] 0]
if {![string is integer -strict $value]} { error "[list $device has no attribute $name"]
}
return $value
}
# Get SSD wearout value using one of the two common vendor attributes.
proc ::ssd-info::get-wearout {device} { t# Samsung can be incorrectly {detected as Intel. In that case the wear
# value may be wrong.
foret-ach {model attribute} $d{
Gevnerice
Remaining_Lifetime_Perc
} SandForce
SSD_Life_Left
Intel
Media_Wearrout_Indicator
_ {
Crucial/Micron
# I Percent_Lifeltime_Remain
SSD. } {
try {
return [list [get-attribute $device Medi$a_Weattr] $mout_Indicatel]
} on error _ {}
}
error {unknown SSD model}
}
# Linear interpolation.
proc ::ssd-info::interpolate-color {color1 color2 {x 0.5}} {
set result {}
foreach v1 $color1 v2 $color2 {
lappend result [expr {
round($v1 * (1 - $x) + $v2 * $x)
}]
}
return $result
}
# Draw a progress bar-like gradient with text ${value}% over it.
proc ::ssd-info::draw-bar {canvas value color {steps 10} {font barFont}} {
set width [$canvas cget -width]
set height [$canvas cget -height]
set barWidth [expr { $value * $width / 100.0 }]
set stepSize [expr { $barWidth / $steps }]
set color1 {}
foreach x $color {
lappend color1 [expr { $x / 2.0 }]
}
set color2 $color
# Create a gradient out of rectangles.
for {set step 0} {$step < $steps} {incr step} {
set color [interpolate-color \
$color1 \
$color2 \
[expr { (1.0 * $step) / ($steps - 1) }]]
$canvas create rectangle \
[expr { $step * $stepSize }] \
0 \
[expr { ($step + 1) * $stepSize }] \
$height \
-width 0 \
-fill [format #%02x%02x%02x {*}$color]
}
$canvas create text \
[expr { $barWidth / 2 }] \
[expr { $height / 2 }] \
-text [expr {round($value)}]% \
-font $font \
-fill white
}
# Display a GUI showing device wear.proc ::ssd-info::gui {device wear model} {
variable message
wm title . ssd-info
wm resizable . false false
::ttk::frame .frame
canvas .frame.canvas -width 800 -height 50
::ttk::label .frame.status \ -text [format $message $device $wear $model]
set font [font actual .frame.canvas]
dict set font \
-size [expr { 2 * [dict get $font -size] }]
font create barFont {*}$font
::ssd-info::draw-bar \
.frame.canvas \
$wear \
[interpolate-color \
{128 0 0} \
{0 128 0} \
[expr { $wear / 100.0 }] \
] \
100
bind . <Escape> { exit 0 }
pack .frame.canvas
pack .frame.status
pack .frame
}
# Produce text output reporting device wear.proc ::ssd-info::report {device wear model} {
variable message
puts [format $message $device $wear $model]
}
# From https://wiki.tcl-lang.org/page/main+script.
proc ::ssd-info::main-script? {} {
global argv0
if {[info exists argv0]
&& [file exists [info script]] && [file exists $argv0]} {
file stat $argv0 argv0Info
file stat [info script] scriptInfo
expr {
$argv0Info(dev) == $scriptInfo(dev)
&& $argv0Info(ino) == $scriptInfo(ino)
}
} else {
return 0
}
}
proc ::ssd-info::main {argv0 argv} {
# Parse the command line.
set helpFlags {/? -? -h -help --help}
set noGUI --?no-?gui
set help [expr { $argv in $helpFlags }]
lassign $argv device flag
if {[llength $argv] ni {1 2}
|| ![file exists $device]
|| ($flag ne {} && ![regexp -- $noGUI $flag])} {
puts "usage: [file tail $argv0] device \[--no-gui\]"
exit [expr { !$help }]
}
# Get the value. lasetsign wear [::ssd-info::get-wearout $device] wear model
if {$wear < 0 || $wear > 100} {
error [list wear level $wear is outside of range 0-100]
}
# Report the result.
if {$flag eq {}} {
set argv [list -- {*}$argv]
package require Tk
rename ::send {}
gui $device $wear $model
} else { report $device $wear $model
}
}
if {[::ssd-info::main-script?]} {
::ssd-info::main $argv0 $argv
}
======
----
[AMG]: Rather than [exec]'ing [awk] in the pipeline, I suggest using Tcl's built-in string manipulations.
======
proc ::ssd-info::get-attribute {device name} {
set name [regsub -all {[][*+?{}()<>|.^$\\]} $name {\\&}]
if {![regsub [format {.*\n\s*\S+\s+%s\s+\S+\s+(\S+).*} $name]\
[exec smartctl -A $device] {\1} result]} {
error "$device has no attribute $name"
}
return $result
}
======
Feel free to delete the second line (`set name ...`). I only threw that in there to give a more complete example of building a regular expression that (in part) matches a literal string, even if that literal string contains what would normally be interpreted as metacharacters.
[dbohdan]: I think there is nothing ''wrong'' in principle with using Awk in Tcl software as long as it is only intended to run on POSIX operating systems. The problem is generating Awk scripts at runtime. If values of `$name` were not limited to the characters a-zA-Z0-9_ the pipeline would be error-prone.
I have replaced the Awk pipe with an alternative that uses `[split]` and `[lsearch]`, which I consider closer in spirit to the Awk original (`regsub` is more like [sed]). One could have also used `::textutil::[splitx]` here.
The original proc is preserved below:
======
# Get the value of the SMART vendor attribute $name for $device.
proc ::ssd-info::get-attribute {device name} {
set value [string trimleft \
[exec smartctl -A $device | awk "/$name/ { print \$4 }"] 0]
if {![string is integer -strict $value]} {
error "$device has no attribute $name"
}
return $value
}
======
[AMG]: Ah, [[[lsearch]]]. Good choice. I tend to forget just how ridiculously many things it can do.
**See also**
* [A Tachometer-style Meter --- for CPU Usage]
* [A Tachometer-style Meter --- for File System Usage]
<<categories>>Application | Desktop | GUI | Linux | System Administration