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); }