@cryptotaxi247 / netdata-1 / commits / 537bab0f1

Keep precompiled statements for alarm log queries to improve performance (#16321)

* Keep precompiled statements for alarm log queries to improve performance * Check bind result

Stelios Fragkakis committed Nov 6, 2023 at 08:53 UTC 537bab0f18d77c8ea21daf7a960ebc294179233a
3 files changed +181 -154
database/sqlite/sqlite_health.c
+178 -151
@@ -1270,184 +1270,211 @@ done:
1270 "hl.units, hld.info, hld.exec_code, hld.new_status, hld.old_status, hld.delay, hld.new_value, hld.old_value, " \
1271 "hld.last_repeat, ah.class, ah.component, ah.type, hl.chart_context, hld.transition_id, hld.summary " \
1272 "FROM health_log hl, alert_hash ah, health_log_detail hld WHERE hl.config_hash_id = ah.hash_id and " \
1273 - "hl.health_log_id = hld.health_log_id and hl.host_id = @host_id "
1273 + "hl.health_log_id = hld.health_log_id and hl.host_id = @host_id AND hld.unique_id > @after "
1274
1275 -void sql_health_alarm_log2json(RRDHOST *host, BUFFER *wb, uint32_t after, char *chart) {
1275 +void sql_health_alarm_log2json(RRDHOST *host, BUFFER *wb, time_t after, const char *chart)
1276 +{
1277 + buffer_strcat(wb, "[");
1278
1277 - buffer_strcat(wb, "[");
1279 + unsigned int max = host->health_log.max;
1280
1279 - unsigned int max = host->health_log.max;
1280 - unsigned int count = 0;
1281 + static __thread sqlite3_stmt *stmt_no_chart = NULL;
1282 + static __thread sqlite3_stmt *stmt_with_chart = NULL;
1283
1282 - sqlite3_stmt *res = NULL;
1283 - int rc;
1284 + sqlite3_stmt **active_stmt;
1285 + sqlite3_stmt *stmt_query;
1286
1285 - BUFFER *command = buffer_create(MAX_HEALTH_SQL_SIZE, NULL);
1286 - buffer_sprintf(command, SQL_SELECT_HEALTH_LOG);
1287 + int count = 0, rc;
1288
1288 - if (chart) {
1289 - char chart_sql[MAX_HEALTH_SQL_SIZE + 1];
1290 - snprintfz(chart_sql, MAX_HEALTH_SQL_SIZE, "AND hl.chart = '%s' ", chart);
1291 - buffer_strcat(command, chart_sql);
1292 - }
1289 + active_stmt = chart ? &stmt_with_chart : &stmt_no_chart;
1290
1294 - if (after) {
1295 - char after_sql[MAX_HEALTH_SQL_SIZE + 1];
1296 - snprintfz(after_sql, MAX_HEALTH_SQL_SIZE, "AND hld.unique_id > %u ", after);
1297 - buffer_strcat(command, after_sql);
1298 - }
1291 + if (!*active_stmt) {
1292
1300 - {
1301 - char limit_sql[MAX_HEALTH_SQL_SIZE + 1];
1302 - snprintfz(limit_sql, MAX_HEALTH_SQL_SIZE, "ORDER BY hld.unique_id DESC LIMIT %u ", max);
1303 - buffer_strcat(command, limit_sql);
1304 - }
1293 + BUFFER *command = buffer_create(MAX_HEALTH_SQL_SIZE, NULL);
1294 + buffer_sprintf(command, SQL_SELECT_HEALTH_LOG);
1295
1306 - rc = sqlite3_prepare_v2(db_meta, buffer_tostring(command), -1, &res, 0);
1307 - if (unlikely(rc != SQLITE_OK)) {
1308 - error_report("Failed to prepare statement SQL_SELECT_HEALTH_LOG");
1309 - buffer_free(command);
1310 - return;
1311 - }
1296 + if (chart)
1297 + buffer_strcat(command, " AND hl.chart = @chart ");
1298
1313 - rc = sqlite3_bind_blob(res, 1, &host->host_uuid, sizeof(host->host_uuid), SQLITE_STATIC);
1314 - if (unlikely(rc != SQLITE_OK)) {
1315 - error_report("Failed to bind host_id for SQL_SELECT_HEALTH_LOG.");
1316 - sqlite3_finalize(res);
1317 - buffer_free(command);
1318 - return;
1319 - }
1299 + buffer_strcat(command, " ORDER BY hld.unique_id DESC LIMIT @limit");
1300
1321 - while (sqlite3_step(res) == SQLITE_ROW) {
1301 + rc = prepare_statement(db_meta, buffer_tostring(command), active_stmt);
1302 + buffer_free(command);
1303
1323 - char old_value_string[100 + 1];
1324 - char new_value_string[100 + 1];
1304 + if (unlikely(rc != SQLITE_OK)) {
1305 + error_report("Failed to prepare statement SQL_SELECT_HEALTH_LOG");
1306 + buffer_strcat(wb, "\n]");
1307 + return;
1308 + }
1309 + }
1310
1326 - char config_hash_id[UUID_STR_LEN];
1327 - uuid_unparse_lower(*((uuid_t *) sqlite3_column_blob(res, 3)), config_hash_id);
1311 + stmt_query = *active_stmt;
1312
1329 - char transition_id[UUID_STR_LEN] = {0};
1330 - if (sqlite3_column_type(res, 30) != SQLITE_NULL)
1331 - uuid_unparse_lower(*((uuid_t *) sqlite3_column_blob(res, 30)), transition_id);
1313 + int param = 0;
1314 + rc = sqlite3_bind_blob(stmt_query, ++param, &host->host_uuid, sizeof(host->host_uuid), SQLITE_STATIC);
1315 + if (unlikely(rc != SQLITE_OK)) {
1316 + error_report("Failed to bind host_id for SQL_SELECT_HEALTH_LOG.");
1317 + goto finish;
1318 + }
1319 +
1320 + rc = sqlite3_bind_int64(stmt_query, ++param, after);
1321 + if (unlikely(rc != SQLITE_OK)) {
1322 + error_report("Failed to bind after for SQL_SELECT_HEALTH_LOG.");
1323 + goto finish;
1324 + }
1325 +
1326 + if (chart) {
1327 + rc = sqlite3_bind_text(stmt_query, ++param, chart, -1, SQLITE_STATIC);
1328 + if (unlikely(rc != SQLITE_OK)) {
1329 + error_report("Failed to bind after for SQL_SELECT_HEALTH_LOG.");
1330 + goto finish;
1331 + }
1332 + }
1333 +
1334 + rc = sqlite3_bind_int64(stmt_query, ++param, max);
1335 + if (unlikely(rc != SQLITE_OK)) {
1336 + error_report("Failed to bind max lines for SQL_SELECT_HEALTH_LOG.");
1337 + goto finish;
1338 + }
1339 +
1340 + while (sqlite3_step(stmt_query) == SQLITE_ROW) {
1341 + char old_value_string[100 + 1];
1342 + char new_value_string[100 + 1];
1343 +
1344 + char config_hash_id[UUID_STR_LEN];
1345 + uuid_unparse_lower(*((uuid_t *)sqlite3_column_blob(stmt_query, 3)), config_hash_id);
1346
1333 - char *edit_command = sqlite3_column_bytes(res, 16) > 0 ? health_edit_command_from_source((char *)sqlite3_column_text(res, 16)) : strdupz("UNKNOWN=0=UNKNOWN");
1347 + char transition_id[UUID_STR_LEN] = {0};
1348 + if (sqlite3_column_type(stmt_query, 30) != SQLITE_NULL)
1349 + uuid_unparse_lower(*((uuid_t *)sqlite3_column_blob(stmt_query, 30)), transition_id);
1350
1335 - if (count)
1351 + char *edit_command = sqlite3_column_bytes(stmt_query, 16) > 0 ?
1352 + health_edit_command_from_source((char *)sqlite3_column_text(stmt_query, 16)) :
1353 + strdupz("UNKNOWN=0=UNKNOWN");
1354 +
1355 + if (count)
1356 buffer_sprintf(wb, ",");
1357
1338 - count++;
1339 -
1340 - buffer_sprintf(
1341 - wb,
1342 - "\n\t{\n"
1343 - "\t\t\"hostname\": \"%s\",\n"
1344 - "\t\t\"utc_offset\": %d,\n"
1345 - "\t\t\"timezone\": \"%s\",\n"
1346 - "\t\t\"unique_id\": %u,\n"
1347 - "\t\t\"alarm_id\": %u,\n"
1348 - "\t\t\"alarm_event_id\": %u,\n"
1349 - "\t\t\"config_hash_id\": \"%s\",\n"
1350 - "\t\t\"transition_id\": \"%s\",\n"
1351 - "\t\t\"name\": \"%s\",\n"
1352 - "\t\t\"chart\": \"%s\",\n"
1353 - "\t\t\"context\": \"%s\",\n"
1354 - "\t\t\"class\": \"%s\",\n"
1355 - "\t\t\"component\": \"%s\",\n"
1356 - "\t\t\"type\": \"%s\",\n"
1357 - "\t\t\"processed\": %s,\n"
1358 - "\t\t\"updated\": %s,\n"
1359 - "\t\t\"exec_run\": %lu,\n"
1360 - "\t\t\"exec_failed\": %s,\n"
1361 - "\t\t\"exec\": \"%s\",\n"
1362 - "\t\t\"recipient\": \"%s\",\n"
1363 - "\t\t\"exec_code\": %d,\n"
1364 - "\t\t\"source\": \"%s\",\n"
1365 - "\t\t\"command\": \"%s\",\n"
1366 - "\t\t\"units\": \"%s\",\n"
1367 - "\t\t\"when\": %lu,\n"
1368 - "\t\t\"duration\": %lu,\n"
1369 - "\t\t\"non_clear_duration\": %lu,\n"
1370 - "\t\t\"status\": \"%s\",\n"
1371 - "\t\t\"old_status\": \"%s\",\n"
1372 - "\t\t\"delay\": %d,\n"
1373 - "\t\t\"delay_up_to_timestamp\": %lu,\n"
1374 - "\t\t\"updated_by_id\": %u,\n"
1375 - "\t\t\"updates_id\": %u,\n"
1376 - "\t\t\"value_string\": \"%s\",\n"
1377 - "\t\t\"old_value_string\": \"%s\",\n"
1378 - "\t\t\"last_repeat\": %lu,\n"
1379 - "\t\t\"silenced\": \"%s\",\n",
1380 - rrdhost_hostname(host),
1381 - host->utc_offset,
1382 - rrdhost_abbrev_timezone(host),
1383 - (unsigned int) sqlite3_column_int64(res, 0),
1384 - (unsigned int) sqlite3_column_int64(res, 1),
1385 - (unsigned int) sqlite3_column_int64(res, 2),
1386 - config_hash_id,
1387 - transition_id,
1388 - sqlite3_column_text(res, 12),
1389 - sqlite3_column_text(res, 13),
1390 - sqlite3_column_text(res, 29),
1391 - sqlite3_column_text(res, 26) ? (const char *) sqlite3_column_text(res, 26) : (char *) "Unknown",
1392 - sqlite3_column_text(res, 27) ? (const char *) sqlite3_column_text(res, 27) : (char *) "Unknown",
1393 - sqlite3_column_text(res, 28) ? (const char *) sqlite3_column_text(res, 28) : (char *) "Unknown",
1394 - (sqlite3_column_int64(res, 9) & HEALTH_ENTRY_FLAG_PROCESSED)?"true":"false",
1395 - (sqlite3_column_int64(res, 9) & HEALTH_ENTRY_FLAG_UPDATED)?"true":"false",
1396 - (long unsigned int)sqlite3_column_int64(res, 10),
1397 - (sqlite3_column_int64(res, 9) & HEALTH_ENTRY_FLAG_EXEC_FAILED)?"true":"false",
1398 - sqlite3_column_text(res, 14) ? (const char *) sqlite3_column_text(res, 14) : string2str(host->health.health_default_exec),
1399 - sqlite3_column_text(res, 15) ? (const char *) sqlite3_column_text(res, 15) : string2str(host->health.health_default_recipient),
1400 - sqlite3_column_int(res, 19),
1401 - sqlite3_column_text(res, 16) ? (const char *) sqlite3_column_text(res, 16) : (char *) "Unknown",
1402 - edit_command,
1403 - sqlite3_column_text(res, 17),
1404 - (long unsigned int)sqlite3_column_int64(res, 6),
1405 - (long unsigned int)sqlite3_column_int64(res, 7),
1406 - (long unsigned int)sqlite3_column_int64(res, 8),
1407 - rrdcalc_status2string(sqlite3_column_int(res, 20)),
1408 - rrdcalc_status2string(sqlite3_column_int(res, 21)),
1409 - sqlite3_column_int(res, 22),
1410 - (long unsigned int)sqlite3_column_int64(res, 11),
1411 - (unsigned int)sqlite3_column_int64(res, 4),
1412 - (unsigned int)sqlite3_column_int64(res, 5),
1413 - sqlite3_column_type(res, 23) == SQLITE_NULL ? "-" : format_value_and_unit(new_value_string, 100, sqlite3_column_double(res, 23), (char *) sqlite3_column_text(res, 17), -1),
1414 - sqlite3_column_type(res, 24) == SQLITE_NULL ? "-" : format_value_and_unit(old_value_string, 100, sqlite3_column_double(res, 24), (char *) sqlite3_column_text(res, 17), -1),
1415 - (long unsigned int)sqlite3_column_int64(res, 25),
1416 - (sqlite3_column_int64(res, 9) & HEALTH_ENTRY_FLAG_SILENCED)?"true":"false");
1417 -
1418 - health_string2json(wb, "\t\t", "summary", (char *) sqlite3_column_text(res, 31), ",\n");
1419 - health_string2json(wb, "\t\t", "info", (char *) sqlite3_column_text(res, 18), ",\n");
1420 -
1421 - if(unlikely(sqlite3_column_int64(res, 9) & HEALTH_ENTRY_FLAG_NO_CLEAR_NOTIFICATION)) {
1358 + count++;
1359 +
1360 + buffer_sprintf(
1361 + wb,
1362 + "\n\t{\n"
1363 + "\t\t\"hostname\": \"%s\",\n"
1364 + "\t\t\"utc_offset\": %d,\n"
1365 + "\t\t\"timezone\": \"%s\",\n"
1366 + "\t\t\"unique_id\": %u,\n"
1367 + "\t\t\"alarm_id\": %u,\n"
1368 + "\t\t\"alarm_event_id\": %u,\n"
1369 + "\t\t\"config_hash_id\": \"%s\",\n"
1370 + "\t\t\"transition_id\": \"%s\",\n"
1371 + "\t\t\"name\": \"%s\",\n"
1372 + "\t\t\"chart\": \"%s\",\n"
1373 + "\t\t\"context\": \"%s\",\n"
1374 + "\t\t\"class\": \"%s\",\n"
1375 + "\t\t\"component\": \"%s\",\n"
1376 + "\t\t\"type\": \"%s\",\n"
1377 + "\t\t\"processed\": %s,\n"
1378 + "\t\t\"updated\": %s,\n"
1379 + "\t\t\"exec_run\": %lu,\n"
1380 + "\t\t\"exec_failed\": %s,\n"
1381 + "\t\t\"exec\": \"%s\",\n"
1382 + "\t\t\"recipient\": \"%s\",\n"
1383 + "\t\t\"exec_code\": %d,\n"
1384 + "\t\t\"source\": \"%s\",\n"
1385 + "\t\t\"command\": \"%s\",\n"
1386 + "\t\t\"units\": \"%s\",\n"
1387 + "\t\t\"when\": %lu,\n"
1388 + "\t\t\"duration\": %lu,\n"
1389 + "\t\t\"non_clear_duration\": %lu,\n"
1390 + "\t\t\"status\": \"%s\",\n"
1391 + "\t\t\"old_status\": \"%s\",\n"
1392 + "\t\t\"delay\": %d,\n"
1393 + "\t\t\"delay_up_to_timestamp\": %lu,\n"
1394 + "\t\t\"updated_by_id\": %u,\n"
1395 + "\t\t\"updates_id\": %u,\n"
1396 + "\t\t\"value_string\": \"%s\",\n"
1397 + "\t\t\"old_value_string\": \"%s\",\n"
1398 + "\t\t\"last_repeat\": %lu,\n"
1399 + "\t\t\"silenced\": \"%s\",\n",
1400 + rrdhost_hostname(host),
1401 + host->utc_offset,
1402 + rrdhost_abbrev_timezone(host),
1403 + (unsigned int)sqlite3_column_int64(stmt_query, 0),
1404 + (unsigned int)sqlite3_column_int64(stmt_query, 1),
1405 + (unsigned int)sqlite3_column_int64(stmt_query, 2),
1406 + config_hash_id,
1407 + transition_id,
1408 + sqlite3_column_text(stmt_query, 12),
1409 + sqlite3_column_text(stmt_query, 13),
1410 + sqlite3_column_text(stmt_query, 29),
1411 + sqlite3_column_text(stmt_query, 26) ? (const char *)sqlite3_column_text(stmt_query, 26) : (char *)"Unknown",
1412 + sqlite3_column_text(stmt_query, 27) ? (const char *)sqlite3_column_text(stmt_query, 27) : (char *)"Unknown",
1413 + sqlite3_column_text(stmt_query, 28) ? (const char *)sqlite3_column_text(stmt_query, 28) : (char *)"Unknown",
1414 + (sqlite3_column_int64(stmt_query, 9) & HEALTH_ENTRY_FLAG_PROCESSED) ? "true" : "false",
1415 + (sqlite3_column_int64(stmt_query, 9) & HEALTH_ENTRY_FLAG_UPDATED) ? "true" : "false",
1416 + (long unsigned int)sqlite3_column_int64(stmt_query, 10),
1417 + (sqlite3_column_int64(stmt_query, 9) & HEALTH_ENTRY_FLAG_EXEC_FAILED) ? "true" : "false",
1418 + sqlite3_column_text(stmt_query, 14) ? (const char *)sqlite3_column_text(stmt_query, 14) :
1419 + string2str(host->health.health_default_exec),
1420 + sqlite3_column_text(stmt_query, 15) ? (const char *)sqlite3_column_text(stmt_query, 15) :
1421 + string2str(host->health.health_default_recipient),
1422 + sqlite3_column_int(stmt_query, 19),
1423 + sqlite3_column_text(stmt_query, 16) ? (const char *)sqlite3_column_text(stmt_query, 16) : (char *)"Unknown",
1424 + edit_command,
1425 + sqlite3_column_text(stmt_query, 17),
1426 + (long unsigned int)sqlite3_column_int64(stmt_query, 6),
1427 + (long unsigned int)sqlite3_column_int64(stmt_query, 7),
1428 + (long unsigned int)sqlite3_column_int64(stmt_query, 8),
1429 + rrdcalc_status2string(sqlite3_column_int(stmt_query, 20)),
1430 + rrdcalc_status2string(sqlite3_column_int(stmt_query, 21)),
1431 + sqlite3_column_int(stmt_query, 22),
1432 + (long unsigned int)sqlite3_column_int64(stmt_query, 11),
1433 + (unsigned int)sqlite3_column_int64(stmt_query, 4),
1434 + (unsigned int)sqlite3_column_int64(stmt_query, 5),
1435 + sqlite3_column_type(stmt_query, 23) == SQLITE_NULL ?
1436 + "-" :
1437 + format_value_and_unit(
1438 + new_value_string, 100, sqlite3_column_double(stmt_query, 23), (char *)sqlite3_column_text(stmt_query, 17), -1),
1439 + sqlite3_column_type(stmt_query, 24) == SQLITE_NULL ?
1440 + "-" :
1441 + format_value_and_unit(
1442 + old_value_string, 100, sqlite3_column_double(stmt_query, 24), (char *)sqlite3_column_text(stmt_query, 17), -1),
1443 + (long unsigned int)sqlite3_column_int64(stmt_query, 25),
1444 + (sqlite3_column_int64(stmt_query, 9) & HEALTH_ENTRY_FLAG_SILENCED) ? "true" : "false");
1445 +
1446 + health_string2json(wb, "\t\t", "summary", (char *)sqlite3_column_text(stmt_query, 31), ",\n");
1447 + health_string2json(wb, "\t\t", "info", (char *)sqlite3_column_text(stmt_query, 18), ",\n");
1448 +
1449 + if (unlikely(sqlite3_column_int64(stmt_query, 9) & HEALTH_ENTRY_FLAG_NO_CLEAR_NOTIFICATION)) {
1450 buffer_strcat(wb, "\t\t\"no_clear_notification\": true,\n");
1423 - }
1451 + }
1452
1425 - buffer_strcat(wb, "\t\t\"value\":");
1426 - if (sqlite3_column_type(res, 23) == SQLITE_NULL)
1453 + buffer_strcat(wb, "\t\t\"value\":");
1454 + if (sqlite3_column_type(stmt_query, 23) == SQLITE_NULL)
1455 buffer_strcat(wb, "null");
1428 - else
1429 - buffer_print_netdata_double(wb, sqlite3_column_double(res, 23));
1430 - buffer_strcat(wb, ",\n");
1456 + else
1457 + buffer_print_netdata_double(wb, sqlite3_column_double(stmt_query, 23));
1458 + buffer_strcat(wb, ",\n");
1459
1432 - buffer_strcat(wb, "\t\t\"old_value\":");
1433 - if (sqlite3_column_type(res, 24) == SQLITE_NULL)
1460 + buffer_strcat(wb, "\t\t\"old_value\":");
1461 + if (sqlite3_column_type(stmt_query, 24) == SQLITE_NULL)
1462 buffer_strcat(wb, "null");
1435 - else
1436 - buffer_print_netdata_double(wb, sqlite3_column_double(res, 24));
1437 - buffer_strcat(wb, "\n");
1463 + else
1464 + buffer_print_netdata_double(wb, sqlite3_column_double(stmt_query, 24));
1465 + buffer_strcat(wb, "\n");
1466
1439 - buffer_strcat(wb, "\t}");
1467 + buffer_strcat(wb, "\t}");
1468
1441 - freez(edit_command);
1442 - }
1469 + freez(edit_command);
1470 + }
1471
1444 - buffer_strcat(wb, "\n]");
1472 +finish:
1473 + buffer_strcat(wb, "\n]");
1474
1446 - rc = sqlite3_finalize(res);
1447 - if (unlikely(rc != SQLITE_OK))
1448 - error_report("Failed to finalize statement for SQL_SELECT_HEALTH_LOG");
1449 -
1450 - buffer_free(command);
1475 + rc = sqlite3_reset(stmt_query);
1476 + if (unlikely(rc != SQLITE_OK))
1477 + error_report("Failed to finalize statement for SQL_SELECT_HEALTH_LOG");
1478 }
1479
1480 #define SQL_COPY_HEALTH_LOG(table) "INSERT OR IGNORE INTO health_log (host_id, alarm_id, config_hash_id, name, chart, family, exec, recipient, units, chart_context) SELECT ?1, alarm_id, config_hash_id, name, chart, family, exec, recipient, units, chart_context from %s;", table
database/sqlite/sqlite_health.h
+1 -1
@@ -13,7 +13,7 @@ void sql_health_alarm_log_cleanup(RRDHOST *host, bool claimed);
13 int alert_hash_and_store_config(uuid_t hash_id, struct alert_config *cfg, int store_hash);
14 void sql_aclk_alert_clean_dead_entries(RRDHOST *host);
15 int sql_health_get_last_executed_event(RRDHOST *host, ALARM_ENTRY *ae, RRDCALC_STATUS *last_executed_status);
16 -void sql_health_alarm_log2json(RRDHOST *host, BUFFER *wb, uint32_t after, char *chart);
16 +void sql_health_alarm_log2json(RRDHOST *host, BUFFER *wb, time_t after, const char *chart);
17 int health_migrate_old_health_log_table(char *table);
18 uint32_t sql_get_alarm_id(RRDHOST *host, STRING *chart, STRING *name, uint32_t *next_event_id, uuid_t *config_hash_id);
19 uint32_t sql_get_alarm_id_check_zero_hash(RRDHOST *host, STRING *chart, STRING *name, uint32_t *next_event_id, uuid_t *config_hash_id);
web/api/web_api_v1.c
+2 -2
@@ -444,7 +444,7 @@ inline int web_client_api_request_v1_alarm_count(RRDHOST *host, struct web_clien
444 }
445
446 inline int web_client_api_request_v1_alarm_log(RRDHOST *host, struct web_client *w, char *url) {
447 - uint32_t after = 0;
447 + time_t after = 0;
448 char *chart = NULL;
449
450 while(url) {
@@ -455,7 +455,7 @@ inline int web_client_api_request_v1_alarm_log(RRDHOST *host, struct web_client
455 if(!name || !*name) continue;
456 if(!value || !*value) continue;
457
458 - if (!strcmp(name, "after")) after = (uint32_t)strtoul(value, NULL, 0);
458 + if (!strcmp(name, "after")) after = (time_t) strtoul(value, NULL, 0);
459 else if (!strcmp(name, "chart")) chart = value;
460 }
461