diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b7ce03774414..9666207545b17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - [bind](/plugins/inputs/bind/README.md) - Contributed by @dswarbrick & @danielllek - [ecs](/plugins/inputs/ecs/README.md) - Contributed by @rbtr - [github](/plugins/inputs/github/README.md) - Contributed by @influxdata +- [openweathermap](/plugins/inputs/openweathermap/README.md) - Contributed by @regel - [powerdns_recursor](/plugins/inputs/powerdns_recursor/README.md) - Contributed by @dupondje #### New Aggregators diff --git a/README.md b/README.md index 45edae1e06cc4..d3adf1e2d1ca4 100644 --- a/README.md +++ b/README.md @@ -236,6 +236,7 @@ For documentation on the latest development code see the [documentation index][d * [nvidia_smi](./plugins/inputs/nvidia_smi) * [openldap](./plugins/inputs/openldap) * [opensmtpd](./plugins/inputs/opensmtpd) +* [openweathermap](./plugins/inputs/openweathermap) * [pf](./plugins/inputs/pf) * [pgbouncer](./plugins/inputs/pgbouncer) * [phpfpm](./plugins/inputs/phpfpm) diff --git a/plugins/inputs/openweathermap/README.md b/plugins/inputs/openweathermap/README.md index 7b781b129b8c8..d796990495a38 100644 --- a/plugins/inputs/openweathermap/README.md +++ b/plugins/inputs/openweathermap/README.md @@ -1,73 +1,68 @@ -# Telegraf Plugin: openweathermap +# OpenWeatherMap Input Plugin -OpenWeatherMap provides the current weather and forecasts for more than 200,000 cities. To use this plugin you will need a token. For more information [click here](https://openweathermap.org/appid). +Collect current weather and forecast data from OpenWeatherMap. -Find city identifiers in this [list](http://bulk.openweathermap.org/sample/city.list.json.gz). You can also use this [url](https://openweathermap.org/find) as an alternative to downloading a file. The ID is in the url of the city: `https://openweathermap.org/city/2643743` +To use this plugin you will need an [api key][] (app_id). -### Configuration: +City identifiers can be found in the [city list][]. Alternately you can +[search][] by name; the `city_id` can be found as the last digits of the URL: +https://openweathermap.org/city/2643743 + +### Configuration ```toml [[inputs.openweathermap]] - ## Root url of API to pull stats - # base_url = "https://api.openweathermap.org/data/2.5/" - ## Your personal user token from openweathermap.org - # app_id = "xxxxxxxxxxxxxxxxxxxxxxx" - ## List of city identifiers - # city_id = ["2988507", "519188"] - ## HTTP response timeout (default: 5s) + ## OpenWeatherMap API key. + app_id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + + ## City ID's to collect weather data from. + city_id = ["5391959"] + + ## APIs to fetch; can contain "weather" or "forecast". + fetch = ["weather", "forecast"] + + ## OpenWeatherMap base URL + # base_url = "https://api.openweathermap.org/" + + ## Timeout for HTTP response. # response_timeout = "5s" - ## Query the current weather and future forecast - # fetch = ["weather", "forecast"] - ## For temperature in Fahrenheit use units=imperial - ## For temperature in Celsius use units=metric (default) + + ## Preferred unit system for temperature and wind speed. Can be one of + ## "metric", "imperial", or "standard". # units = "metric" + + ## Query interval; OpenWeatherMap weather data is updated every 10 + ## minutes. + interval = "10m" ``` -### Metrics: +### Metrics -+ weather - - fields: - - humidity (int, Humidity percentage) - - temperature (float, Unit: Celcius) - - pressure (float, Atmospheric pressure in hPa) - - rain (float, Rain volume for the last 3 hours, mm) - - wind_speed (float, Wind speed. Unit Default: meter/sec) - - wind_degrees (float, Wind direction, degrees) +- weather - tags: - city_id - forecast + - fields: + - cloudiness (int, percent) + - humidity (int, percent) + - pressure (float, atmospheric pressure hPa) + - rain (float, rain volume for the last 3 hours in mm) + - sunrise (int, nanoseconds since unix epoch) + - sunset (int, nanoseconds since unix epoch) + - temperature (float, degrees) + - visibility (int, meters, not available on forecast data) + - wind_degrees (float, wind direction in degrees) + - wind_speed (float, wind speed in meters/sec or miles/sec) -### Example Output: -Using this configuration: -```toml -[[inputs.openweathermap]] - base_url = "https://api.openweathermap.org/data/2.5/" - app_id = "change_this_with_your_appid" - city_id = ["2988507", "519188"] - response_timeout = "5s" - fetch = ["weather", "forecast"] - units = "metric" -``` - -When run with: -``` -./telegraf -config telegraf.conf -input-filter openweathermap -test -``` +### Example Output -It produces data similar to: ``` -> weather,city_id=4303602,forecast=* humidity=51i,pressure=1012,rain=0,temperature=16.410000000000025,wind_degrees=170,wind_speed=2.6 1556393944000000000 -> weather,city_id=2988507,forecast=* humidity=87i,pressure=1020,rain=0,temperature=7.110000000000014,wind_degrees=260,wind_speed=5.1 1556393841000000000 -> weather,city_id=2988507,forecast=3h humidity=69i,pressure=1020.38,rain=0,temperature=5.650000000000034,wind_degrees=268.456,wind_speed=5.83 1556398800000000000 -> weather,city_id=2988507,forecast=* humidity=69i,pressure=1020.38,rain=0,temperature=5.650000000000034,wind_degrees=268.456,wind_speed=5.83 1556398800000000000 -> weather,city_id=2988507,forecast=6h humidity=74i,pressure=1020.87,rain=0,temperature=5.810000000000002,wind_degrees=261.296,wind_speed=5.43 1556409600000000000 -> weather,city_id=2988507,forecast=* humidity=74i,pressure=1020.87,rain=0,temperature=5.810000000000002,wind_degrees=261.296,wind_speed=5.43 1556409600000000000 -> weather,city_id=4303602,forecast=9h humidity=66i,pressure=1010.63,rain=0,temperature=14.740000000000009,wind_degrees=196.264,wind_speed=4.3 1556398800000000000 -> weather,city_id=4303602,forecast=* humidity=66i,pressure=1010.63,rain=0,temperature=14.740000000000009,wind_degrees=196.264,wind_speed=4.3 1556398800000000000 +> weather,city=San\ Francisco,city_id=5391959,country=US,forecast=* cloudiness=40i,humidity=72i,pressure=1013,rain=0,sunrise=1559220629000000000i,sunset=1559273058000000000i,temperature=13.31,visibility=16093i,wind_degrees=280,wind_speed=4.6 1559268695000000000 +> weather,city=San\ Francisco,city_id=5391959,country=US,forecast=3h cloudiness=0i,humidity=86i,pressure=1012.03,rain=0,temperature=10.69,wind_degrees=222.855,wind_speed=2.76 1559271600000000000 +> weather,city=San\ Francisco,city_id=5391959,country=US,forecast=6h cloudiness=11i,humidity=93i,pressure=1012.79,rain=0,temperature=9.34,wind_degrees=212.685,wind_speed=1.85 1559282400000000000 ``` - - - - +[api key]: https://openweathermap.org/appid +[city list]: http://bulk.openweathermap.org/sample/city.list.json.gz +[search]: https://openweathermap.org/find diff --git a/plugins/inputs/openweathermap/openweathermap.go b/plugins/inputs/openweathermap/openweathermap.go index 1c246d0b63b56..c15ee3832edc5 100644 --- a/plugins/inputs/openweathermap/openweathermap.go +++ b/plugins/inputs/openweathermap/openweathermap.go @@ -1,9 +1,10 @@ package openweathermap import ( - "bufio" "encoding/json" "fmt" + "io" + "mime" "net/http" "net/url" "strconv" @@ -16,37 +17,50 @@ import ( "github.com/influxdata/telegraf/plugins/inputs" ) +const ( + // https://openweathermap.org/current#severalid + // Call for several city IDs + // The limit of locations is 20. + owmRequestSeveralCityId int = 20 + + defaultBaseURL = "https://api.openweathermap.org/" + defaultResponseTimeout time.Duration = time.Second * 5 + defaultUnits string = "metric" +) + type OpenWeatherMap struct { - BaseUrl string - AppId string - CityId []string + AppId string `toml:"app_id"` + CityId []string `toml:"city_id"` + Fetch []string `toml:"fetch"` + BaseUrl string `toml:"base_url"` + ResponseTimeout internal.Duration `toml:"response_timeout"` + Units string `toml:"units"` client *http.Client - - ResponseTimeout internal.Duration - Fetch []string - Units string } -// https://openweathermap.org/current#severalid -// Call for several city IDs -// The limit of locations is 20. -const owmRequestSeveralCityId int = 20 -const defaultResponseTimeout time.Duration = time.Second * 5 -const defaultUnits string = "metric" - var sampleConfig = ` - ## Root url of weather map REST API - base_url = "https://api.openweathermap.org/" - ## Your personal user token from openweathermap.org + ## OpenWeatherMap API key. app_id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - city_id = ["2988507", "2988588"] - ## HTTP response timeout (default: 5s) - response_timeout = "5s" + ## City ID's to collect weather data from. + city_id = ["5391959"] + + ## APIs to fetch; can contain "weather" or "forecast". fetch = ["weather", "forecast"] - units = "metric" - ## Limit OpenWeatherMap query interval. See calls per minute info at: https://openweathermap.org/price + + ## OpenWeatherMap base URL + # base_url = "https://api.openweathermap.org/" + + ## Timeout for HTTP response. + # response_timeout = "5s" + + ## Preferred unit system for temperature and wind speed. Can be one of + ## "metric", "imperial", or "standard". + # units = "metric" + + ## Query interval; OpenWeatherMap updates their weather data every 10 + ## minutes. interval = "10m" ` @@ -69,7 +83,6 @@ func (n *OpenWeatherMap) Gather(acc telegraf.Accumulator) error { // Create an HTTP client that is re-used for each // collection interval - if n.client == nil { client, err := n.createHttpClient() if err != nil { @@ -77,33 +90,43 @@ func (n *OpenWeatherMap) Gather(acc telegraf.Accumulator) error { } n.client = client } + units := n.Units - if units == "" { + switch n.Units { + case "imperial", "standard": + break + default: units = defaultUnits } + for _, fetch := range n.Fetch { if fetch == "forecast" { var u *url.URL - var addr *url.URL for _, city := range n.CityId { u, err = url.Parse(fmt.Sprintf("/data/2.5/forecast?id=%s&APPID=%s&units=%s", city, n.AppId, units)) if err != nil { - acc.AddError(fmt.Errorf("Unable to parse address '%s': %s", u, err)) + acc.AddError(fmt.Errorf("unable to parse address '%s': %s", u, err)) continue } - addr = base.ResolveReference(u) + + addr := base.ResolveReference(u).String() wg.Add(1) - go func(addr *url.URL) { + go func() { defer wg.Done() - acc.AddError(n.gatherUrl(addr, acc, true)) - }(addr) + status, err := n.gatherUrl(addr) + if err != nil { + acc.AddError(err) + return + } + + gatherForecast(acc, status) + }() } } else if fetch == "weather" { j := 0 for j < len(n.CityId) { var u *url.URL - var addr *url.URL strs = make([]string, 0) for i := 0; j < len(n.CityId) && i < owmRequestSeveralCityId; i++ { strs = append(strs, n.CityId[j]) @@ -117,12 +140,18 @@ func (n *OpenWeatherMap) Gather(acc telegraf.Accumulator) error { continue } - addr = base.ResolveReference(u) + addr := base.ResolveReference(u).String() wg.Add(1) - go func(addr *url.URL) { + go func() { defer wg.Done() - acc.AddError(n.gatherUrl(addr, acc, false)) - }(addr) + status, err := n.gatherUrl(addr) + if err != nil { + acc.AddError(err) + return + } + + gatherWeather(acc, status) + }() } } @@ -133,7 +162,6 @@ func (n *OpenWeatherMap) Gather(acc telegraf.Accumulator) error { } func (n *OpenWeatherMap) createHttpClient() (*http.Client, error) { - if n.ResponseTimeout.Duration < time.Second { n.ResponseTimeout.Duration = defaultResponseTimeout } @@ -146,65 +174,51 @@ func (n *OpenWeatherMap) createHttpClient() (*http.Client, error) { return client, nil } -func (n *OpenWeatherMap) gatherUrl(addr *url.URL, acc telegraf.Accumulator, forecast bool) error { - resp, err := n.client.Get(addr.String()) - +func (n *OpenWeatherMap) gatherUrl(addr string) (*Status, error) { + resp, err := n.client.Get(addr) if err != nil { - return fmt.Errorf("error making HTTP request to %s: %s", addr.String(), err) + return nil, fmt.Errorf("error making HTTP request to %s: %s", addr, err) } defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { - return fmt.Errorf("%s returned HTTP status %s", addr.String(), resp.Status) + return nil, fmt.Errorf("%s returned HTTP status %s", addr, resp.Status) } - contentType := strings.Split(resp.Header.Get("Content-Type"), ";")[0] - switch contentType { - case "application/json": - err = gatherWeatherUrl(bufio.NewReader(resp.Body), forecast, acc) - return err - default: - return fmt.Errorf("%s returned unexpected content type %s", addr.String(), contentType) + + mediaType, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) + if err != nil { + return nil, err + } + + if mediaType != "application/json" { + return nil, fmt.Errorf("%s returned unexpected content type %s", addr, mediaType) } + + return gatherWeatherUrl(resp.Body) } type WeatherEntry struct { - Dt int64 `json:"dt"` - Dttxt string `json:"dt_txt"` // empty for weather/ + Dt int64 `json:"dt"` Clouds struct { All int64 `json:"all"` } `json:"clouds"` Main struct { - GrndLevel float64 `json:"grnd_level"` // empty for weather/ - Humidity int64 `json:"humidity"` - SeaLevel float64 `json:"sea_level"` // empty for weather/ - Pressure float64 `json:"pressure"` - Temp float64 `json:"temp"` - TempMax float64 `json:"temp_max"` - TempMin float64 `json:"temp_min"` + Humidity int64 `json:"humidity"` + Pressure float64 `json:"pressure"` + Temp float64 `json:"temp"` } `json:"main"` Rain struct { Rain3 float64 `json:"3h"` } `json:"rain"` Sys struct { - Pod string `json:"pod"` - Country string `json:"country"` - Message float64 `json:"message"` - Id int64 `json:"id"` - Type int64 `json:"type"` - Sunrise int64 `json:"sunrise"` - Sunset int64 `json:"sunset"` + Country string `json:"country"` + Sunrise int64 `json:"sunrise"` + Sunset int64 `json:"sunset"` } `json:"sys"` Wind struct { Deg float64 `json:"deg"` Speed float64 `json:"speed"` } `json:"wind"` - Weather []struct { - Description string `json:"description"` - Icon string `json:"icon"` - Id int64 `json:"id"` - Main string `json:"main"` - } `json:"weather"` - - // Additional entries for weather/ Id int64 `json:"id"` Name string `json:"name"` Coord struct { @@ -227,69 +241,66 @@ type Status struct { List []WeatherEntry `json:"list"` } -func gatherWeatherUrl(r *bufio.Reader, forecast bool, acc telegraf.Accumulator) error { +func gatherWeatherUrl(r io.Reader) (*Status, error) { dec := json.NewDecoder(r) status := &Status{} if err := dec.Decode(status); err != nil { - return fmt.Errorf("Error while decoding JSON response: %s", err) + return nil, fmt.Errorf("error while decoding JSON response: %s", err) } - status.Gather(forecast, acc) - return nil + return status, nil } -func (s *Status) Gather(forecast bool, acc telegraf.Accumulator) { - tags := map[string]string{ - "city_id": strconv.FormatInt(s.City.Id, 10), - "forecast": "*", - } - - for i, e := range s.List { +func gatherWeather(acc telegraf.Accumulator, status *Status) { + for _, e := range status.List { tm := time.Unix(e.Dt, 0) - if e.Id > 0 { - tags["city_id"] = strconv.FormatInt(e.Id, 10) - } - if forecast { - tags["forecast"] = fmt.Sprintf("%dh", (i+1)*3) - } acc.AddFields( "weather", map[string]interface{}{ + "cloudiness": e.Clouds.All, + "humidity": e.Main.Humidity, + "pressure": e.Main.Pressure, "rain": e.Rain.Rain3, + "sunrise": time.Unix(e.Sys.Sunrise, 0).UnixNano(), + "sunset": time.Unix(e.Sys.Sunset, 0).UnixNano(), + "temperature": e.Main.Temp, + "visibility": e.Visibility, "wind_degrees": e.Wind.Deg, "wind_speed": e.Wind.Speed, + }, + map[string]string{ + "city": e.Name, + "city_id": strconv.FormatInt(e.Id, 10), + "country": e.Sys.Country, + "forecast": "*", + }, + tm) + } +} + +func gatherForecast(acc telegraf.Accumulator, status *Status) { + tags := map[string]string{ + "city_id": strconv.FormatInt(status.City.Id, 10), + "forecast": "*", + "city": status.City.Name, + "country": status.City.Country, + } + for i, e := range status.List { + tm := time.Unix(e.Dt, 0) + tags["forecast"] = fmt.Sprintf("%dh", (i+1)*3) + acc.AddFields( + "weather", + map[string]interface{}{ + "cloudiness": e.Clouds.All, "humidity": e.Main.Humidity, "pressure": e.Main.Pressure, + "rain": e.Rain.Rain3, "temperature": e.Main.Temp, + "wind_degrees": e.Wind.Deg, + "wind_speed": e.Wind.Speed, }, tags, tm) } - if forecast { - // intentional: overwrite future data points - // under the * tag - tags := map[string]string{ - "city_id": strconv.FormatInt(s.City.Id, 10), - "forecast": "*", - } - for _, e := range s.List { - tm := time.Unix(e.Dt, 0) - if e.Id > 0 { - tags["city_id"] = strconv.FormatInt(e.Id, 10) - } - acc.AddFields( - "weather", - map[string]interface{}{ - "rain": e.Rain.Rain3, - "wind_degrees": e.Wind.Deg, - "wind_speed": e.Wind.Speed, - "humidity": e.Main.Humidity, - "pressure": e.Main.Pressure, - "temperature": e.Main.Temp, - }, - tags, - tm) - } - } } func init() { @@ -300,6 +311,7 @@ func init() { return &OpenWeatherMap{ ResponseTimeout: tmout, Units: defaultUnits, + BaseUrl: defaultBaseURL, } }) } diff --git a/plugins/inputs/openweathermap/openweathermap_test.go b/plugins/inputs/openweathermap/openweathermap_test.go index 98f0a64a2bcbe..d59766dd6b7de 100644 --- a/plugins/inputs/openweathermap/openweathermap_test.go +++ b/plugins/inputs/openweathermap/openweathermap_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/testutil" "github.com/stretchr/testify/require" ) @@ -105,6 +106,9 @@ const groupWeatherResponse = ` { "cnt": 1, "list": [{ + "clouds": { + "all": 0 + }, "coord": { "lat": 48.85, "lon": 2.35 @@ -282,13 +286,20 @@ func TestForecastGeneratesMetrics(t *testing.T) { var acc testutil.Accumulator - err_openweathermap := n.Gather(&acc) - require.NoError(t, err_openweathermap) - for _, forecast_tag := range []string{"*", "3h"} { - acc.AssertContainsTaggedFields( - t, + err := n.Gather(&acc) + require.NoError(t, err) + + expected := []telegraf.Metric{ + testutil.MustMetric( "weather", + map[string]string{ + "city_id": "2988507", + "forecast": "3h", + "city": "Paris", + "country": "FR", + }, map[string]interface{}{ + "cloudiness": int64(88), "humidity": int64(91), "pressure": 1018.65, "temperature": 6.71, @@ -296,16 +307,18 @@ func TestForecastGeneratesMetrics(t *testing.T) { "wind_degrees": 228.501, "wind_speed": 3.76, }, + time.Unix(1543622400, 0), + ), + testutil.MustMetric( + "weather", map[string]string{ "city_id": "2988507", - "forecast": forecast_tag, - }) - } - for _, forecast_tag := range []string{"*", "6h"} { - acc.AssertContainsTaggedFields( - t, - "weather", + "forecast": "6h", + "city": "Paris", + "country": "FR", + }, map[string]interface{}{ + "cloudiness": int64(92), "humidity": int64(98), "pressure": 1032.18, "temperature": 6.38, @@ -313,11 +326,13 @@ func TestForecastGeneratesMetrics(t *testing.T) { "wind_degrees": 335.005, "wind_speed": 2.66, }, - map[string]string{ - "city_id": "2988507", - "forecast": forecast_tag, - }) + time.Unix(1544043600, 0), + ), } + + testutil.RequireMetricsEqual(t, + expected, acc.GetTelegrafMetrics(), + testutil.SortMetrics()) } func TestWeatherGeneratesMetrics(t *testing.T) { @@ -346,25 +361,34 @@ func TestWeatherGeneratesMetrics(t *testing.T) { var acc testutil.Accumulator - err_openweathermap := n.Gather(&acc) + err := n.Gather(&acc) + require.NoError(t, err) - require.NoError(t, err_openweathermap) - - acc.AssertContainsTaggedFields( - t, - "weather", - map[string]interface{}{ - "humidity": int64(87), - "pressure": 1007.0, - "temperature": 9.25, - "wind_degrees": 290.0, - "wind_speed": 8.7, - "rain": 0.0, - }, - map[string]string{ - "city_id": "2988507", - "forecast": "*", - }) + expected := []telegraf.Metric{ + testutil.MustMetric( + "weather", + map[string]string{ + "city_id": "2988507", + "forecast": "*", + "city": "Paris", + "country": "FR", + }, + map[string]interface{}{ + "cloudiness": int64(0), + "humidity": int64(87), + "pressure": 1007.0, + "temperature": 9.25, + "rain": 0.0, + "sunrise": int64(1544167818000000000), + "sunset": int64(1544198047000000000), + "wind_degrees": 290.0, + "wind_speed": 8.7, + "visibility": 10000, + }, + time.Unix(1544194800, 0), + ), + } + testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics()) } func TestBatchWeatherGeneratesMetrics(t *testing.T) { @@ -393,90 +417,78 @@ func TestBatchWeatherGeneratesMetrics(t *testing.T) { var acc testutil.Accumulator - err_openweathermap := n.Gather(&acc) - - require.NoError(t, err_openweathermap) - - acc.AssertContainsTaggedFields( - t, - "weather", - map[string]interface{}{ - "humidity": int64(46), - "pressure": 1014.0, - "temperature": 9.57, - "wind_degrees": 60.0, - "wind_speed": 5.0, - "rain": 0.0, - }, - map[string]string{ - "city_id": "524901", - "forecast": "*", - }) - acc.AssertContainsTaggedFields( - t, - "weather", - map[string]interface{}{ - "humidity": int64(63), - "pressure": 1009.0, - "temperature": 19.29, - "wind_degrees": 0.0, - "wind_speed": 1.0, - "rain": 0.0, - }, - map[string]string{ - "city_id": "703448", - "forecast": "*", - }) - acc.AssertContainsTaggedFields( - t, - "weather", - map[string]interface{}{ - "humidity": int64(66), - "pressure": 1019.0, - "temperature": 10.62, - "wind_degrees": 290.0, - "wind_speed": 6.2, - "rain": 0.072, - }, - map[string]string{ - "city_id": "2643743", - "forecast": "*", - }) -} - -func TestResponseTimeout(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var rsp string - if r.URL.Path == "/data/2.5/group" { - rsp = groupWeatherResponse - w.Header()["Content-Type"] = []string{"application/json"} - } else if r.URL.Path == "/data/2.5/forecast" { - rsp = sampleStatusResponse - w.Header()["Content-Type"] = []string{"application/json"} - } else { - panic("Cannot handle request") - } - - time.Sleep(time.Second * 6) // Cause timeout - fmt.Fprintln(w, rsp) - })) - defer ts.Close() + err := n.Gather(&acc) + require.NoError(t, err) - n := &OpenWeatherMap{ - BaseUrl: ts.URL, - AppId: "noappid", - CityId: []string{"2988507"}, - Fetch: []string{"weather"}, - Units: "metric", + expected := []telegraf.Metric{ + testutil.MustMetric( + "weather", + map[string]string{ + "city_id": "524901", + "forecast": "*", + "city": "Moscow", + "country": "RU", + }, + map[string]interface{}{ + "cloudiness": 40, + "humidity": int64(46), + "pressure": 1014.0, + "temperature": 9.57, + "wind_degrees": 60.0, + "wind_speed": 5.0, + "rain": 0.0, + "sunrise": int64(1556416455000000000), + "sunset": int64(1556470779000000000), + "visibility": 10000, + }, + time.Unix(1556444155, 0), + ), + testutil.MustMetric( + "weather", + map[string]string{ + "city_id": "703448", + "forecast": "*", + "city": "Kiev", + "country": "UA", + }, + map[string]interface{}{ + "cloudiness": 0, + "humidity": int64(63), + "pressure": 1009.0, + "temperature": 19.29, + "wind_degrees": 0.0, + "wind_speed": 1.0, + "rain": 0.0, + "sunrise": int64(1556419155000000000), + "sunset": int64(1556471486000000000), + "visibility": 10000, + }, + time.Unix(1556444155, 0), + ), + testutil.MustMetric( + "weather", + map[string]string{ + "city_id": "2643743", + "forecast": "*", + "city": "London", + "country": "GB", + }, + map[string]interface{}{ + "cloudiness": 75, + "humidity": int64(66), + "pressure": 1019.0, + "temperature": 10.62, + "wind_degrees": 290.0, + "wind_speed": 6.2, + "rain": 0.072, + "sunrise": int64(1556426319000000000), + "sunset": int64(1556479032000000000), + "visibility": 10000, + }, + time.Unix(1556444155, 0), + ), } - - var acc testutil.Accumulator - - err_openweathermap := n.Gather(&acc) - - require.NoError(t, err_openweathermap) - acc.AssertDoesNotContainMeasurement( - t, - "weather", - ) + testutil.RequireMetricsEqual(t, + expected, acc.GetTelegrafMetrics(), + testutil.SortMetrics()) } diff --git a/testutil/accumulator.go b/testutil/accumulator.go index b5021010a16c4..e33959a83ec9b 100644 --- a/testutil/accumulator.go +++ b/testutil/accumulator.go @@ -320,26 +320,6 @@ func (a *Accumulator) WaitError(n int) { a.Unlock() } -func (a *Accumulator) assertContainsTaggedFields( - t *testing.T, - measurement string, - fields map[string]interface{}, - tags map[string]string, -) { - for _, p := range a.Metrics { - if !reflect.DeepEqual(tags, p.Tags) { - continue - } - - if p.Measurement == measurement { - assert.Equal(t, fields, p.Fields) - return - } - } - msg := fmt.Sprintf("unknown measurement %s with tags %v", measurement, tags) - assert.Fail(t, msg) -} - func (a *Accumulator) AssertContainsTaggedFields( t *testing.T, measurement string, @@ -357,7 +337,8 @@ func (a *Accumulator) AssertContainsTaggedFields( return } } - a.assertContainsTaggedFields(t, measurement, fields, tags) + msg := fmt.Sprintf("unknown measurement %s with tags %v", measurement, tags) + assert.Fail(t, msg) } func (a *Accumulator) AssertDoesNotContainsTaggedFields(