Prometheus Exporter for USGS Water Data service
- 4 minutes read - 836 wordsI’m a little obsessed with creating Prometheus Exporters:
- Prometheus Exporter for Azure
- Prometheus Exporter for crt.sh
- Prometheus Exporter for Fly.io
- Prometheus Exporter for GoatCounter
- Prometheus Exporter for Google Cloud
- Prometheus Exporter for Koyeb
- Prometheus Exporter for Linode
- Prometheus Exporter for PorkBun
- Prometheus Exporter for updown.io
- Prometheus Exporter for Vultr
All of these were written to scratch an itch.
In the case of the cloud platform exporters (Azure, Fly, Google, Linode, Vultr etc.), it’s an overriding anxiety that I’ll leave resources deployed on these platforms and, running an exporter that ships alerts to Pushover and Gmail, provides me a support mechanism for me.
The latest is:
USGS Water Data service
In the case of USGS Water Data actually Instantaneous Values service, the “itch” was to be able to interact with gauge (gage) data for the Snoqualmie river which floods from snow melt and after heavy rain.
NOTE The USGS uses the spelling “Gage” instead of “Gauge”
When I stumbled upon the service via King County’s Flood warning system: Snoqualmie Basin and e.g. Duvall and finally to Gage: 12150400, I was slightly concerned to see that it uses XML data. Not that I didn’t love XML back in the day (early 2000s) but I dislike it more than JSON in 2025. Fortunately, the service provides JSON formatting too (requests and responses) and there’s a useful test service
Using the test service, it was easy to assemble a query for some of the data that interests me:
https://waterservices.usgs.gov/nwis/iv/?format=json&sites=12150400&siteStatus=all
NOTE recreating the URL for this post, I realize I should probably use
siteStatus=active
But this returns a wealth of data see response
The exporter includes a limited (not all fields and not all types!) SDK implementation of Instantaneous Values service in waterdata
sufficient to invoke the above URL (for arbitrary sites) and return a (mostly) parsed value.
The exporter comprises a single metric (GageHeightFeet
) from the service (variableCode: 00065
) and generates a Prometheus metric called usgs_waterdata_iv_gage_height
:
Insights
I don’t fully understand the structure of the service’s data. I assume (but haven’t found) it documented.
Credentials
The service does not require API keys or other credentials.
Gage Height
It’s measured in feet which always feels a little… unscientific.
It’s specifically defined by variableCode
value 00065
(string) and ID 45807202
Multiple timeseries are returned by the query and must be filtered for the above value.
Site Codes
The Snoqualmie River at Duvall has siteCode
value “12150400” (string)..
I assume there’s some lookup feature that maps these values to names but have been navigating the site to uncover these.
Values
Of the single timeseries returned that matches gage height (00065
), there can be multiple values
(I’ve called these TimeSeries_Values
) returned. I’m unsure what differentiates each of the values (it’s not time). For ease, I’ve been selecting the zero-th.
Each value contains further values
(I’ve called these TimeSeries_Values_Values
) and each of these corresponds to a time period. For ease, I’ve been selecting the zero-th value (again).
The final value is the measurement. The value is a string type. Strings are often used in JSON data as a way to preserve floating point values cross-platform but this meaans that the value must be parsed into a float64
to be published as a Prometheus metric (using the Golang SDK).
Response
{
"name": "ns1:timeSeriesResponseType",
"declaredType": "org.cuahsi.waterml.TimeSeriesResponseType",
"scope": "javax.xml.bind.JAXBElement$GlobalScope",
"value": {
"queryInfo": {
"queryURL": "http://waterservices.usgs.gov/nwis/iv/format=json&sites=12150400&siteStatus=active",
"criteria": {
"locationParam": "[ALL:12150400]",
"variableParam": "ALL",
"parameter": []
},
"note": [
{
"value": "[ALL:12150400]",
"title": "filter:sites"
},
{
"value": "[mode=LATEST, modifiedSince=null]",
"title": "filter:timeRange"
},
{
"value": "methodIds=[ALL]",
"title": "filter:methodId"
},
{
"value": "2025-02-27T17:15:55.082Z",
"title": "requestDT"
},
{
"value": "80d94c80-f52e-11ef-ad7b-005056beda50",
"title": "requestId"
},
{
"value": "Provisional data are subject to revision. Go to http://waterdata.usgs.gov/nwis/help/?provisional for more information.",
"title": "disclaimer"
},
{
"value": "caas01",
"title": "server"
}
]
},
"timeSeries": [
{
"sourceInfo": {
"siteName": "SNOQUALMIE RIVER AT DUVALL, WA",
"siteCode": [
{
"value": "12150400",
"network": "NWIS",
"agencyCode": "USGS"
}
],
"timeZoneInfo": {
"defaultTimeZone": {
"zoneOffset": "-08:00",
"zoneAbbreviation": "PST"
},
"daylightSavingsTimeZone": {
"zoneOffset": "-07:00",
"zoneAbbreviation": "PDT"
},
"siteUsesDaylightSavingsTime": true
},
"geoLocation": {
"geogLocation": {
"srs": "EPSG:4326",
"latitude": 47.74315509,
"longitude": -121.9879004
},
"localSiteXY": []
},
"note": [],
"siteType": [],
"siteProperty": [
{
"value": "ST",
"name": "siteTypeCd"
},
{
"value": "17110010",
"name": "hucCd"
},
{
"value": "53",
"name": "stateCd"
},
{
"value": "53033",
"name": "countyCd"
}
]
},
"variable": {
"variableCode": [
{
"value": "00065",
"network": "NWIS",
"vocabulary": "NWIS:UnitValues",
"variableID": 45807202,
"default": true
}
],
"variableName": "Gage height, ft",
"variableDescription": "Gage height, feet",
"valueType": "Derived Value",
"unit": {
"unitCode": "ft"
},
"options": {
"option": [
{
"name": "Statistic",
"optionCode": "00000"
}
]
},
"note": [],
"noDataValue": -999999,
"variableProperty": [],
"oid": "45807202"
},
"values": [
{
"value": [
{
"value": "28.56",
"qualifiers": [
"P"
],
"dateTime": "2025-02-27T08:30:00.000-08:00"
}
],
"qualifier": [
{
"qualifierCode": "P",
"qualifierDescription": "Provisional data subject to revision.",
"qualifierID": 0,
"network": "NWIS",
"vocabulary": "uv_rmk_cd"
}
],
"qualityControlLevel": [],
"method": [
{
"methodDescription": "",
"methodID": 151241
}
],
"source": [],
"offset": [],
"sample": [],
"censorCode": []
}
],
"name": "USGS:12150400:00065:00000"
}
]
},
"nil": false,
"globalScope": true,
"typeSubstituted": false
}