Radek Chalupa   konzultace a školení programování, vývoj software na zakázku

Jak v Linuxu zjistit počet zapsaných bytů na SSD disku

2.9.2019

Asi všichni výrobci SSD disků poskytují vlastní software, který kromě různých optimalizačních nástrojů umožňuje zjistit aktuální počet zapsaných bytů a tím (ve vztahu k deklarované hodnotě maximálního počtu "zapsatelných" bytů) odhadnout další životnost disku. Bohužel v naprosté většině případů je tento software pouze pro Windows. Pokud tedy nemáte na počítači vedle Linuxu také Windows, nastává otázka jak výše uvedené hodnoty zjistit, pokud pomineme variantu dočasného fyzického připojení disku k jinému počítači s Windows.

Nástroj smartctl

Na internetu lze najít různé linuxové aplikace "třetích stran", které toto umí. Pokud ale z nějakého důvodu (třeba obavy o bezpečnost) nechcete instalovat software z více či méně neznámého zdroje, musíme si vystačit s tím co poskytují repositáře té které distribuce.

Asi všechny "velké" distribuce by měly mít v repositáři balíček smartmontools, který obsahuje konzolovou aplikaci smartctl.

Tento program je nutné spouštět jako root, tj. s administrátorskými právy. Pro vypsání všech SMART hodnot použijeme parametr -a, za kterým následuje požadované zařízení ve tvaru /dev/sd[x], tj. např.

# smartctl -a /dev/sda. 

Ve výpisu kromě informací o disku uvidíme seznam SMART atributů, mezi nimiž by měl být Total_LBAs_Written, měl by mít identifikátor 241. Pokud chceme vypsat pouze tento atribut, použijeme v příkazu grep:

# smartctl -a /dev/sda | grep Total_LBAs_Written

Interpretace získané hodnoty

V posledním sloupci (RAW_VALUE) je hodnota, kterou nyní musíme nějak interpretovat. Bohužel mezi různými výrobci neexistuje jediný standard, v jakých jednotkách tuto hodnotu ukládat, resp. zobrazovat ve SMART výstupu. V technických parametrech toho kterého disku, které lze najít na webu, by mělo být uvedeno jak tuto hodnotu interpretovat. Osobní zkušenost mám s SSD disky značek Samsung, Kingston a AData, takže s předpokladem že výrobce to má stejné u všech svých disků, mohu uvést následující způsoby výpočtu.

Pro disky Samsung by RAW_VALUE měla znamenat počat zapsaných sektorů, takže pokud ji vynásobíme hodnotou uvedenou jako "Sector Size" (najdeme ji v plném SMART výpisu na začátku mezi informacemi o zařízení), která bude nejspíš 512 dostaneme hodnotu v bytech. Pak už jen stačí posunout desetinnou tečku vlevo o příslušný počet řádů, abychom dostali hodnotu v TB nebo GB, popř. pokud chceme "počítačové" giga/tera byty místo matematicko-fyzikálních vydělit hodnotou (1024*1024*1024) pro GiB, resp. (1024*1024*1024*1024) pro TiB.

U disků Kingston by RAW_VALUE měla být přímo hodnotou v GB, takže zde je výpočet triviální.

U disků AData by hodnota RAW_VALUE měla být počet zapsaných 32MB bloků, takže celkový počet bytů získáme jejím vynásobením hodnotou (32*1024*1024) a GB nebo TB pak příslušným převodem.

Jednoduchý program v C++

Pro ukázku jsem napsal jednoduchý program v C++, který zavolá smartctl a po dotazu na zařízení a typ disku zobrazí výsledek výpočtu. Najdete ho s překladem ve Visual Studio Code na mém githubu. Následující ukázkový zdrojový kód si samozřejmě můžete upravit podle svých potřeb.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define BUFFER_SIZE 10000
#define TYPE_DEFAULT 1
#define TYPE_SAMSUNG 2
#define TYPE_KINGSTON 3
#define TYPE_ADATA 4

char* _buffer = nullptr;

bool nacist_prikaz(const char* prikaz)
{
	if (!_buffer)
		_buffer = (char*)calloc(BUFFER_SIZE, sizeof(char));
	FILE* p_file = popen(prikaz, "r");
	if (!p_file)
	{
		perror("Chyba otevření\n");
		return false;
	}
	if (!feof(p_file))
	{
		if (!fgets(_buffer, BUFFER_SIZE, p_file))
		{
			free(_buffer);
			pclose(p_file);
			return false;
		}
	}
	pclose(p_file);
	return true;
}

int main(int argc, char** argv)
{
	const double dTB = (double)1024*1024*1024*1024;
	const double dGB = (double)1024*1024*1024;
	const double nAdata = (double)32*1024*1024;
	char device[100];
	char command[255];
	char vstup[255];
	for (;;)
	{
		strcpy(device, "/dev/sda");
		printf("zařízení: %s\n", device);
		printf("Je to ok? (a/n):");
		scanf("%s", &vstup);
		if ((*vstup != 'a') && (*vstup != 'A'))
		{
			printf("Zadej zařízení: ");
			scanf("%s", &device);
		}
		printf("%s\n", device);
		strcpy(command, "smartctl -a ");
		strcat(command, device);
		strcat(command, " | grep Total_LBAs_Written");
		printf("%s\n", command);
		if (!nacist_prikaz(command))
		{
			printf("Chyba! Neznámý příkaz...\n");
			break;
		}
		printf("%s\n", _buffer);
		char* posl_mezera = strrchr((char*)_buffer, ' ');
		if (nullptr == posl_mezera)
		{
			printf("Chyba! Neznámý výstup...\n");
			break;
		}
		long long raw_value = atoll(posl_mezera+1);
		if (0 ==raw_value)
		{
			printf("Chyba! Neznámý výstup...\n");
			break;
		}
		printf("raw value: %s\n", posl_mezera+1);
		printf("----------------------------\n");
		printf("Zadej typ disku:\n");
		printf("Kingston - k:\n");
		printf("Samsung - s:\n");
		printf("Adata - a:\n");
		printf("jiný/neznámý - n:\n");
		printf("konec (bez výpočtu) - q:\n");
		scanf("%s", &vstup);
		if (('k' == *vstup) || ('K' == *vstup))
		{
			printf("Kingston (hodnota v GB): %.2f TB (%d GB)\n", (double)raw_value / 1024, (int)raw_value);
		}
		if (('s' == *vstup) || ('S' == *vstup))
		{
			printf("Samsung (hodnota v 512B): %.2f TB (%.3f GB)\n",
				(double)((raw_value * 512) / (dTB)),
				(double)((raw_value * 512) / (dGB)));
		}
		if (('a' == *vstup) || ('A' == *vstup))
		{
			printf("ADATA (hodnota v 32MB na sektor): %.3f TB (%.3f GB)\n",
				(double)((raw_value * nAdata) / (dTB)),
				(double)((raw_value * nAdata) / (dGB)));
		}
		if (('n' == *vstup) || ('N' == *vstup))
		{
			printf("Neznámý (předpoklad hodnota v 512B): %.2f TB (%.3f GB)\n",
				(double)((raw_value * 512) / (dTB)),
				(double)((raw_value * 512) / (dGB)));
		}
		printf("hotovo\n");
		free(_buffer);
		return EXIT_SUCCESS;
	}
	if (_buffer)
		free(_buffer);
	return EXIT_FAILURE;
}