193 lines
9.6 KiB
Zig
193 lines
9.6 KiB
Zig
const std = @import("std");
|
|
const types = @import("../weather/types.zig");
|
|
const Moon = @import("../Moon.zig");
|
|
const utils = @import("utils.zig");
|
|
|
|
pub fn render(writer: *std.Io.Writer, weather: types.WeatherData, allocator: std.mem.Allocator) !void {
|
|
|
|
// Current conditions
|
|
try writer.print("# HELP temperature_feels_like_celsius Feels Like Temperature in Celsius\n", .{});
|
|
try writer.print("temperature_feels_like_celsius{{forecast=\"current\"}} {d}\n", .{weather.current.feels_like_c});
|
|
|
|
try writer.print("# HELP temperature_feels_like_fahrenheit Feels Like Temperature in Fahrenheit\n", .{});
|
|
try writer.print("temperature_feels_like_fahrenheit{{forecast=\"current\"}} {d}\n", .{weather.current.tempFahrenheit()});
|
|
|
|
try writer.print("# HELP cloudcover_percentage Cloud Coverage in Percent\n", .{});
|
|
try writer.print("cloudcover_percentage{{forecast=\"current\"}} 0\n", .{}); // Not in our data
|
|
|
|
try writer.print("# HELP humidity_percentage Humidity in Percent\n", .{});
|
|
try writer.print("humidity_percentage{{forecast=\"current\"}} {d}\n", .{weather.current.humidity});
|
|
|
|
try writer.print("# HELP precipitation_mm Precipitation (Rainfall) in mm\n", .{});
|
|
try writer.print("precipitation_mm{{forecast=\"current\"}} {d:.1}\n", .{weather.current.precip_mm});
|
|
|
|
try writer.print("# HELP pressure_hpa Air pressure in hPa\n", .{});
|
|
try writer.print("pressure_hpa{{forecast=\"current\"}} {d}\n", .{weather.current.pressure_mb});
|
|
|
|
try writer.print("# HELP temperature_celsius Temperature in Celsius\n", .{});
|
|
try writer.print("temperature_celsius{{forecast=\"current\"}} {d}\n", .{weather.current.temp_c});
|
|
|
|
try writer.print("# HELP temperature_fahrenheit Temperature in Fahrenheit\n", .{});
|
|
try writer.print("temperature_fahrenheit{{forecast=\"current\"}} {d}\n", .{weather.current.tempFahrenheit()});
|
|
|
|
try writer.print("# HELP uv_index Ultraviolet Radiation Index\n", .{});
|
|
try writer.print("uv_index{{forecast=\"current\"}} 0\n", .{}); // Not in our data
|
|
|
|
if (weather.current.visibility_km) |vis| {
|
|
try writer.print("# HELP visibility Visible Distance in Kilometres\n", .{});
|
|
try writer.print("visibility{{forecast=\"current\"}} {d}\n", .{vis});
|
|
}
|
|
|
|
try writer.print("# HELP weather_code Code to describe Weather Condition\n", .{});
|
|
try writer.print("weather_code{{forecast=\"current\"}} {d}\n", .{@intFromEnum(weather.current.weather_code)});
|
|
|
|
try writer.print("# HELP winddir_degree Wind Direction in Degree\n", .{});
|
|
try writer.print("winddir_degree{{forecast=\"current\"}} {d}\n", .{weather.current.wind_deg});
|
|
|
|
try writer.print("# HELP windspeed_kmph Wind Speed in Kilometres per Hour\n", .{});
|
|
try writer.print("windspeed_kmph{{forecast=\"current\"}} {d}\n", .{weather.current.wind_kph});
|
|
|
|
try writer.print("# HELP windspeed_mph Wind Speed in Miles per Hour\n", .{});
|
|
try writer.print("windspeed_mph{{forecast=\"current\"}} {d}\n", .{weather.current.windMph()});
|
|
|
|
try writer.print("# HELP observation_time Minutes since start of the day the observation happened\n", .{});
|
|
try writer.print("observation_time{{forecast=\"current\"}} 0\n", .{}); // Not tracked
|
|
|
|
try writer.print("# HELP weather_desc Weather Description\n", .{});
|
|
try writer.print("weather_desc{{forecast=\"current\", description=\"{s}\"}} 1\n", .{weather.current.condition});
|
|
|
|
try writer.print("# HELP winddir_16_point Wind Direction on a 16-wind compass rose\n", .{});
|
|
const wind_dir = utils.degreeToDirection(weather.current.wind_deg);
|
|
try writer.print("winddir_16_point{{forecast=\"current\", description=\"{s}\"}} 1\n", .{wind_dir});
|
|
|
|
// Forecast days
|
|
for (weather.forecast, 0..) |day, i| {
|
|
const forecast_label = try std.fmt.allocPrint(allocator, "{d}d", .{i});
|
|
defer allocator.free(forecast_label);
|
|
|
|
try writer.print("uv_index{{forecast=\"{s}\"}} 0\n", .{forecast_label}); // Not in our data
|
|
|
|
try writer.print("# HELP temperature_celsius_maximum Maximum Temperature in Celsius\n", .{});
|
|
try writer.print("temperature_celsius_maximum{{forecast=\"{s}\"}} {d}\n", .{ forecast_label, day.max_temp_c });
|
|
|
|
try writer.print("# HELP temperature_fahrenheit_maximum Maximum Temperature in Fahrenheit\n", .{});
|
|
try writer.print("temperature_fahrenheit_maximum{{forecast=\"{s}\"}} {d}\n", .{ forecast_label, day.maxTempFahrenheit() });
|
|
|
|
try writer.print("# HELP temperature_celsius_minimum Minimum Temperature in Celsius\n", .{});
|
|
try writer.print("temperature_celsius_minimum{{forecast=\"{s}\"}} {d}\n", .{ forecast_label, day.min_temp_c });
|
|
|
|
try writer.print("# HELP temperature_fahrenheit_minimum Minimum Temperature in Fahrenheit\n", .{});
|
|
try writer.print("temperature_fahrenheit_minimum{{forecast=\"{s}\"}} {d}\n", .{ forecast_label, day.minTempFahrenheit() });
|
|
|
|
try writer.print("# HELP sun_hour Hours of sunlight\n", .{});
|
|
try writer.print("sun_hour{{forecast=\"{s}\"}} 0.0\n", .{forecast_label}); // Not calculated
|
|
|
|
try writer.print("# HELP snowfall_cm Total snowfall in cm\n", .{});
|
|
try writer.print("snowfall_cm{{forecast=\"{s}\"}} 0.0\n", .{forecast_label}); // Not in our data
|
|
|
|
// Moon phase - use current time for simplicity
|
|
const timestamp = std.time.timestamp();
|
|
const moon = Moon.getPhase(timestamp);
|
|
|
|
try writer.print("# HELP astronomy_moon_illumination Percentage of the moon illuminated\n", .{});
|
|
try writer.print("astronomy_moon_illumination{{forecast=\"{s}\"}} {d}\n", .{ forecast_label, moon.illuminated * 100 });
|
|
|
|
try writer.print("# HELP astronomy_moon_phase Phase of the moon\n", .{});
|
|
try writer.print("astronomy_moon_phase{{forecast=\"{s}\", description=\"{f}\"}} 1\n", .{ forecast_label, moon });
|
|
|
|
try writer.print("# HELP astronomy_moonrise_min Minutes since start of the day until the moon appears above the horizon\n", .{});
|
|
try writer.print("astronomy_moonrise_min{{forecast=\"{s}\"}} 0\n", .{forecast_label}); // Not calculated
|
|
|
|
try writer.print("# HELP astronomy_moonset_min Minutes since start of the day until the moon disappears below the horizon\n", .{});
|
|
try writer.print("astronomy_moonset_min{{forecast=\"{s}\"}} 0\n", .{forecast_label}); // Not calculated
|
|
|
|
try writer.print("# HELP astronomy_sunrise_min Minutes since start of the day until the sun appears above the horizon\n", .{});
|
|
try writer.print("astronomy_sunrise_min{{forecast=\"{s}\"}} 0\n", .{forecast_label}); // Not calculated
|
|
|
|
try writer.print("# HELP astronomy_sunset_min Minutes since start of the day until the moon disappears below the horizon\n", .{});
|
|
try writer.print("astronomy_sunset_min{{forecast=\"{s}\"}} 0\n", .{forecast_label}); // Not calculated
|
|
}
|
|
}
|
|
|
|
test "prometheus format includes required metrics" {
|
|
const allocator = std.testing.allocator;
|
|
|
|
var forecast_days = [_]types.ForecastDay{
|
|
.{
|
|
.date = .{ .year = 2024, .month = .jan, .day = 1 },
|
|
.max_temp_c = 12.0,
|
|
.min_temp_c = 5.0,
|
|
.condition = "Partly cloudy",
|
|
.weather_code = .clouds_scattered,
|
|
.hourly = &[_]types.HourlyForecast{},
|
|
},
|
|
};
|
|
|
|
const weather = types.WeatherData{
|
|
.location = "London",
|
|
.coords = .{ .latitude = 51.5074, .longitude = -0.1278 },
|
|
.current = .{
|
|
.temp_c = 10.0,
|
|
.feels_like_c = 8.0,
|
|
.condition = "Partly cloudy",
|
|
.weather_code = .clouds_scattered,
|
|
.humidity = 75,
|
|
.wind_kph = 15.0,
|
|
.wind_deg = 225.0,
|
|
.pressure_mb = 1013.0,
|
|
.precip_mm = 0.5,
|
|
.visibility_km = 10.0,
|
|
},
|
|
.forecast = &forecast_days,
|
|
.allocator = allocator,
|
|
};
|
|
|
|
var output_buf: [8192]u8 = undefined;
|
|
var writer = std.Io.Writer.fixed(&output_buf);
|
|
|
|
try render(&writer, weather, allocator);
|
|
|
|
const output = output_buf[0..writer.end];
|
|
|
|
// Check for key metrics
|
|
try std.testing.expect(std.mem.indexOf(u8, output, "temperature_celsius{forecast=\"current\"}") != null);
|
|
try std.testing.expect(std.mem.indexOf(u8, output, "humidity_percentage{forecast=\"current\"}") != null);
|
|
try std.testing.expect(std.mem.indexOf(u8, output, "windspeed_kmph{forecast=\"current\"}") != null);
|
|
try std.testing.expect(std.mem.indexOf(u8, output, "temperature_celsius_maximum{forecast=\"0d\"}") != null);
|
|
try std.testing.expect(std.mem.indexOf(u8, output, "astronomy_moon_illumination{forecast=\"0d\"}") != null);
|
|
}
|
|
|
|
test "prometheus format has proper help comments" {
|
|
const allocator = std.testing.allocator;
|
|
|
|
const weather = types.WeatherData{
|
|
.location = "Test",
|
|
.coords = .{ .latitude = 0, .longitude = 0 },
|
|
.current = .{
|
|
.temp_c = 20.0,
|
|
.feels_like_c = 20.0,
|
|
.condition = "Clear",
|
|
.weather_code = .clear,
|
|
.humidity = 50,
|
|
.wind_kph = 10.0,
|
|
.wind_deg = 0.0,
|
|
.pressure_mb = 1000.0,
|
|
.precip_mm = 0.0,
|
|
.visibility_km = null,
|
|
},
|
|
.forecast = &[_]types.ForecastDay{},
|
|
.allocator = allocator,
|
|
};
|
|
|
|
var output_buf: [4096]u8 = undefined;
|
|
var writer = std.Io.Writer.fixed(&output_buf);
|
|
|
|
try render(&writer, weather, allocator);
|
|
|
|
const output = output_buf[0..writer.end];
|
|
|
|
// Check for HELP comments
|
|
try std.testing.expect(std.mem.indexOf(u8, output, "# HELP temperature_celsius Temperature in Celsius") != null);
|
|
try std.testing.expect(std.mem.indexOf(u8, output, "# HELP humidity_percentage Humidity in Percent") != null);
|
|
try std.testing.expect(std.mem.indexOf(u8, output, "# HELP pressure_hpa Air pressure in hPa") != null);
|
|
}
|