Update wifi stats to support multiple stations (#977) (#980)

Signed-off-by: neiledgar <neil.edgar@btinternet.com>
pull/1003/head
neiledgar 6 years ago committed by Ben Kochie
parent 9b97f44a70
commit 7e4d9bd150

@ -2307,31 +2307,39 @@ node_wifi_interface_frequency_hertz{device="wlan0"} 2.412e+09
node_wifi_interface_frequency_hertz{device="wlan1"} 2.412e+09 node_wifi_interface_frequency_hertz{device="wlan1"} 2.412e+09
# HELP node_wifi_station_beacon_loss_total The total number of times a station has detected a beacon loss. # HELP node_wifi_station_beacon_loss_total The total number of times a station has detected a beacon loss.
# TYPE node_wifi_station_beacon_loss_total counter # TYPE node_wifi_station_beacon_loss_total counter
node_wifi_station_beacon_loss_total{device="wlan0"} 1 node_wifi_station_beacon_loss_total{device="wlan0",mac_address="01:02:03:04:05:06"} 2
node_wifi_station_beacon_loss_total{device="wlan0",mac_address="aa:bb:cc:dd:ee:ff"} 1
# HELP node_wifi_station_connected_seconds_total The total number of seconds a station has been connected to an access point. # HELP node_wifi_station_connected_seconds_total The total number of seconds a station has been connected to an access point.
# TYPE node_wifi_station_connected_seconds_total counter # TYPE node_wifi_station_connected_seconds_total counter
node_wifi_station_connected_seconds_total{device="wlan0"} 30 node_wifi_station_connected_seconds_total{device="wlan0",mac_address="01:02:03:04:05:06"} 60
node_wifi_station_connected_seconds_total{device="wlan0",mac_address="aa:bb:cc:dd:ee:ff"} 30
# HELP node_wifi_station_inactive_seconds The number of seconds since any wireless activity has occurred on a station. # HELP node_wifi_station_inactive_seconds The number of seconds since any wireless activity has occurred on a station.
# TYPE node_wifi_station_inactive_seconds gauge # TYPE node_wifi_station_inactive_seconds gauge
node_wifi_station_inactive_seconds{device="wlan0"} 0.4 node_wifi_station_inactive_seconds{device="wlan0",mac_address="01:02:03:04:05:06"} 0.8
node_wifi_station_inactive_seconds{device="wlan0",mac_address="aa:bb:cc:dd:ee:ff"} 0.4
# HELP node_wifi_station_info Labeled WiFi interface station information as provided by the operating system. # HELP node_wifi_station_info Labeled WiFi interface station information as provided by the operating system.
# TYPE node_wifi_station_info gauge # TYPE node_wifi_station_info gauge
node_wifi_station_info{bssid="00:11:22:33:44:55",device="wlan0",mode="client",ssid="Example"} 1 node_wifi_station_info{bssid="00:11:22:33:44:55",device="wlan0",mode="client",ssid="Example"} 1
# HELP node_wifi_station_receive_bits_per_second The current WiFi receive bitrate of a station, in bits per second. # HELP node_wifi_station_receive_bits_per_second The current WiFi receive bitrate of a station, in bits per second.
# TYPE node_wifi_station_receive_bits_per_second gauge # TYPE node_wifi_station_receive_bits_per_second gauge
node_wifi_station_receive_bits_per_second{device="wlan0"} 1.28e+08 node_wifi_station_receive_bits_per_second{device="wlan0",mac_address="01:02:03:04:05:06"} 2.56e+08
node_wifi_station_receive_bits_per_second{device="wlan0",mac_address="aa:bb:cc:dd:ee:ff"} 1.28e+08
# HELP node_wifi_station_signal_dbm The current WiFi signal strength, in decibel-milliwatts (dBm). # HELP node_wifi_station_signal_dbm The current WiFi signal strength, in decibel-milliwatts (dBm).
# TYPE node_wifi_station_signal_dbm gauge # TYPE node_wifi_station_signal_dbm gauge
node_wifi_station_signal_dbm{device="wlan0"} -52 node_wifi_station_signal_dbm{device="wlan0",mac_address="01:02:03:04:05:06"} -26
node_wifi_station_signal_dbm{device="wlan0",mac_address="aa:bb:cc:dd:ee:ff"} -52
# HELP node_wifi_station_transmit_bits_per_second The current WiFi transmit bitrate of a station, in bits per second. # HELP node_wifi_station_transmit_bits_per_second The current WiFi transmit bitrate of a station, in bits per second.
# TYPE node_wifi_station_transmit_bits_per_second gauge # TYPE node_wifi_station_transmit_bits_per_second gauge
node_wifi_station_transmit_bits_per_second{device="wlan0"} 1.64e+08 node_wifi_station_transmit_bits_per_second{device="wlan0",mac_address="01:02:03:04:05:06"} 3.28e+08
node_wifi_station_transmit_bits_per_second{device="wlan0",mac_address="aa:bb:cc:dd:ee:ff"} 1.64e+08
# HELP node_wifi_station_transmit_failed_total The total number of times a station has failed to send a packet. # HELP node_wifi_station_transmit_failed_total The total number of times a station has failed to send a packet.
# TYPE node_wifi_station_transmit_failed_total counter # TYPE node_wifi_station_transmit_failed_total counter
node_wifi_station_transmit_failed_total{device="wlan0"} 2 node_wifi_station_transmit_failed_total{device="wlan0",mac_address="01:02:03:04:05:06"} 4
node_wifi_station_transmit_failed_total{device="wlan0",mac_address="aa:bb:cc:dd:ee:ff"} 2
# HELP node_wifi_station_transmit_retries_total The total number of times a station has had to retry while sending a packet. # HELP node_wifi_station_transmit_retries_total The total number of times a station has had to retry while sending a packet.
# TYPE node_wifi_station_transmit_retries_total counter # TYPE node_wifi_station_transmit_retries_total counter
node_wifi_station_transmit_retries_total{device="wlan0"} 10 node_wifi_station_transmit_retries_total{device="wlan0",mac_address="01:02:03:04:05:06"} 20
node_wifi_station_transmit_retries_total{device="wlan0",mac_address="aa:bb:cc:dd:ee:ff"} 10
# HELP node_xfs_allocation_btree_compares_total Number of allocation B-tree compares for a filesystem. # HELP node_xfs_allocation_btree_compares_total Number of allocation B-tree compares for a filesystem.
# TYPE node_xfs_allocation_btree_compares_total counter # TYPE node_xfs_allocation_btree_compares_total counter
node_xfs_allocation_btree_compares_total{device="sda1"} 0 node_xfs_allocation_btree_compares_total{device="sda1"} 0

@ -2307,31 +2307,39 @@ node_wifi_interface_frequency_hertz{device="wlan0"} 2.412e+09
node_wifi_interface_frequency_hertz{device="wlan1"} 2.412e+09 node_wifi_interface_frequency_hertz{device="wlan1"} 2.412e+09
# HELP node_wifi_station_beacon_loss_total The total number of times a station has detected a beacon loss. # HELP node_wifi_station_beacon_loss_total The total number of times a station has detected a beacon loss.
# TYPE node_wifi_station_beacon_loss_total counter # TYPE node_wifi_station_beacon_loss_total counter
node_wifi_station_beacon_loss_total{device="wlan0"} 1 node_wifi_station_beacon_loss_total{device="wlan0",mac_address="01:02:03:04:05:06"} 2
node_wifi_station_beacon_loss_total{device="wlan0",mac_address="aa:bb:cc:dd:ee:ff"} 1
# HELP node_wifi_station_connected_seconds_total The total number of seconds a station has been connected to an access point. # HELP node_wifi_station_connected_seconds_total The total number of seconds a station has been connected to an access point.
# TYPE node_wifi_station_connected_seconds_total counter # TYPE node_wifi_station_connected_seconds_total counter
node_wifi_station_connected_seconds_total{device="wlan0"} 30 node_wifi_station_connected_seconds_total{device="wlan0",mac_address="01:02:03:04:05:06"} 60
node_wifi_station_connected_seconds_total{device="wlan0",mac_address="aa:bb:cc:dd:ee:ff"} 30
# HELP node_wifi_station_inactive_seconds The number of seconds since any wireless activity has occurred on a station. # HELP node_wifi_station_inactive_seconds The number of seconds since any wireless activity has occurred on a station.
# TYPE node_wifi_station_inactive_seconds gauge # TYPE node_wifi_station_inactive_seconds gauge
node_wifi_station_inactive_seconds{device="wlan0"} 0.4 node_wifi_station_inactive_seconds{device="wlan0",mac_address="01:02:03:04:05:06"} 0.8
node_wifi_station_inactive_seconds{device="wlan0",mac_address="aa:bb:cc:dd:ee:ff"} 0.4
# HELP node_wifi_station_info Labeled WiFi interface station information as provided by the operating system. # HELP node_wifi_station_info Labeled WiFi interface station information as provided by the operating system.
# TYPE node_wifi_station_info gauge # TYPE node_wifi_station_info gauge
node_wifi_station_info{bssid="00:11:22:33:44:55",device="wlan0",mode="client",ssid="Example"} 1 node_wifi_station_info{bssid="00:11:22:33:44:55",device="wlan0",mode="client",ssid="Example"} 1
# HELP node_wifi_station_receive_bits_per_second The current WiFi receive bitrate of a station, in bits per second. # HELP node_wifi_station_receive_bits_per_second The current WiFi receive bitrate of a station, in bits per second.
# TYPE node_wifi_station_receive_bits_per_second gauge # TYPE node_wifi_station_receive_bits_per_second gauge
node_wifi_station_receive_bits_per_second{device="wlan0"} 1.28e+08 node_wifi_station_receive_bits_per_second{device="wlan0",mac_address="01:02:03:04:05:06"} 2.56e+08
node_wifi_station_receive_bits_per_second{device="wlan0",mac_address="aa:bb:cc:dd:ee:ff"} 1.28e+08
# HELP node_wifi_station_signal_dbm The current WiFi signal strength, in decibel-milliwatts (dBm). # HELP node_wifi_station_signal_dbm The current WiFi signal strength, in decibel-milliwatts (dBm).
# TYPE node_wifi_station_signal_dbm gauge # TYPE node_wifi_station_signal_dbm gauge
node_wifi_station_signal_dbm{device="wlan0"} -52 node_wifi_station_signal_dbm{device="wlan0",mac_address="01:02:03:04:05:06"} -26
node_wifi_station_signal_dbm{device="wlan0",mac_address="aa:bb:cc:dd:ee:ff"} -52
# HELP node_wifi_station_transmit_bits_per_second The current WiFi transmit bitrate of a station, in bits per second. # HELP node_wifi_station_transmit_bits_per_second The current WiFi transmit bitrate of a station, in bits per second.
# TYPE node_wifi_station_transmit_bits_per_second gauge # TYPE node_wifi_station_transmit_bits_per_second gauge
node_wifi_station_transmit_bits_per_second{device="wlan0"} 1.64e+08 node_wifi_station_transmit_bits_per_second{device="wlan0",mac_address="01:02:03:04:05:06"} 3.28e+08
node_wifi_station_transmit_bits_per_second{device="wlan0",mac_address="aa:bb:cc:dd:ee:ff"} 1.64e+08
# HELP node_wifi_station_transmit_failed_total The total number of times a station has failed to send a packet. # HELP node_wifi_station_transmit_failed_total The total number of times a station has failed to send a packet.
# TYPE node_wifi_station_transmit_failed_total counter # TYPE node_wifi_station_transmit_failed_total counter
node_wifi_station_transmit_failed_total{device="wlan0"} 2 node_wifi_station_transmit_failed_total{device="wlan0",mac_address="01:02:03:04:05:06"} 4
node_wifi_station_transmit_failed_total{device="wlan0",mac_address="aa:bb:cc:dd:ee:ff"} 2
# HELP node_wifi_station_transmit_retries_total The total number of times a station has had to retry while sending a packet. # HELP node_wifi_station_transmit_retries_total The total number of times a station has had to retry while sending a packet.
# TYPE node_wifi_station_transmit_retries_total counter # TYPE node_wifi_station_transmit_retries_total counter
node_wifi_station_transmit_retries_total{device="wlan0"} 10 node_wifi_station_transmit_retries_total{device="wlan0",mac_address="01:02:03:04:05:06"} 20
node_wifi_station_transmit_retries_total{device="wlan0",mac_address="aa:bb:cc:dd:ee:ff"} 10
# HELP node_xfs_allocation_btree_compares_total Number of allocation B-tree compares for a filesystem. # HELP node_xfs_allocation_btree_compares_total Number of allocation B-tree compares for a filesystem.
# TYPE node_xfs_allocation_btree_compares_total counter # TYPE node_xfs_allocation_btree_compares_total counter
node_xfs_allocation_btree_compares_total{device="sda1"} 0 node_xfs_allocation_btree_compares_total{device="sda1"} 0

@ -1,4 +1,6 @@
[
{ {
"hardwareaddr": "qrvM3e7/",
"connected": 30000000000, "connected": 30000000000,
"inactive": 400000000, "inactive": 400000000,
"receivebitrate": 128000000, "receivebitrate": 128000000,
@ -7,4 +9,17 @@
"transmitretries": 10, "transmitretries": 10,
"transmitfailed": 2, "transmitfailed": 2,
"beaconloss": 1 "beaconloss": 1
},
{
"hardwareaddr": "AQIDBAUG",
"connected": 60000000000,
"inactive": 800000000,
"receivebitrate": 256000000,
"transmitbitrate": 328000000,
"signal": -26,
"transmitretries": 20,
"transmitfailed": 4,
"beaconloss": 2
} }
]

@ -55,7 +55,7 @@ type wifiStater interface {
BSS(ifi *wifi.Interface) (*wifi.BSS, error) BSS(ifi *wifi.Interface) (*wifi.BSS, error)
Close() error Close() error
Interfaces() ([]*wifi.Interface, error) Interfaces() ([]*wifi.Interface, error)
StationInfo(ifi *wifi.Interface) (*wifi.StationInfo, error) StationInfo(ifi *wifi.Interface) ([]*wifi.StationInfo, error)
} }
// NewWifiCollector returns a new Collector exposing Wifi statistics. // NewWifiCollector returns a new Collector exposing Wifi statistics.
@ -65,14 +65,14 @@ func NewWifiCollector() (Collector, error) {
) )
var ( var (
labels = []string{"device"} labels = []string{"device", "mac_address"}
) )
return &wifiCollector{ return &wifiCollector{
interfaceFrequencyHertz: prometheus.NewDesc( interfaceFrequencyHertz: prometheus.NewDesc(
prometheus.BuildFQName(namespace, subsystem, "interface_frequency_hertz"), prometheus.BuildFQName(namespace, subsystem, "interface_frequency_hertz"),
"The current frequency a WiFi interface is operating at, in hertz.", "The current frequency a WiFi interface is operating at, in hertz.",
labels, []string{"device"},
nil, nil,
), ),
@ -193,10 +193,12 @@ func (c *wifiCollector) Update(ch chan<- prometheus.Metric) error {
ifi.Name, err) ifi.Name, err)
} }
info, err := stat.StationInfo(ifi) stations, err := stat.StationInfo(ifi)
switch { switch {
case err == nil: case err == nil:
c.updateStationStats(ch, ifi.Name, info) for _, station := range stations {
c.updateStationStats(ch, ifi.Name, station)
}
case os.IsNotExist(err): case os.IsNotExist(err):
log.Debugf("station information not found for wifi device %q", ifi.Name) log.Debugf("station information not found for wifi device %q", ifi.Name)
default: default:
@ -227,6 +229,7 @@ func (c *wifiCollector) updateStationStats(ch chan<- prometheus.Metric, device s
prometheus.CounterValue, prometheus.CounterValue,
info.Connected.Seconds(), info.Connected.Seconds(),
device, device,
info.HardwareAddr.String(),
) )
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
@ -234,6 +237,7 @@ func (c *wifiCollector) updateStationStats(ch chan<- prometheus.Metric, device s
prometheus.GaugeValue, prometheus.GaugeValue,
info.Inactive.Seconds(), info.Inactive.Seconds(),
device, device,
info.HardwareAddr.String(),
) )
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
@ -241,6 +245,7 @@ func (c *wifiCollector) updateStationStats(ch chan<- prometheus.Metric, device s
prometheus.GaugeValue, prometheus.GaugeValue,
float64(info.ReceiveBitrate), float64(info.ReceiveBitrate),
device, device,
info.HardwareAddr.String(),
) )
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
@ -248,6 +253,7 @@ func (c *wifiCollector) updateStationStats(ch chan<- prometheus.Metric, device s
prometheus.GaugeValue, prometheus.GaugeValue,
float64(info.TransmitBitrate), float64(info.TransmitBitrate),
device, device,
info.HardwareAddr.String(),
) )
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
@ -255,6 +261,7 @@ func (c *wifiCollector) updateStationStats(ch chan<- prometheus.Metric, device s
prometheus.GaugeValue, prometheus.GaugeValue,
float64(info.Signal), float64(info.Signal),
device, device,
info.HardwareAddr.String(),
) )
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
@ -262,6 +269,7 @@ func (c *wifiCollector) updateStationStats(ch chan<- prometheus.Metric, device s
prometheus.CounterValue, prometheus.CounterValue,
float64(info.TransmitRetries), float64(info.TransmitRetries),
device, device,
info.HardwareAddr.String(),
) )
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
@ -269,6 +277,7 @@ func (c *wifiCollector) updateStationStats(ch chan<- prometheus.Metric, device s
prometheus.CounterValue, prometheus.CounterValue,
float64(info.TransmitFailed), float64(info.TransmitFailed),
device, device,
info.HardwareAddr.String(),
) )
ch <- prometheus.MustNewConstMetric( ch <- prometheus.MustNewConstMetric(
@ -276,6 +285,7 @@ func (c *wifiCollector) updateStationStats(ch chan<- prometheus.Metric, device s
prometheus.CounterValue, prometheus.CounterValue,
float64(info.BeaconLoss), float64(info.BeaconLoss),
device, device,
info.HardwareAddr.String(),
) )
} }
@ -346,13 +356,13 @@ func (s *mockWifiStater) Interfaces() ([]*wifi.Interface, error) {
return ifis, nil return ifis, nil
} }
func (s *mockWifiStater) StationInfo(ifi *wifi.Interface) (*wifi.StationInfo, error) { func (s *mockWifiStater) StationInfo(ifi *wifi.Interface) ([]*wifi.StationInfo, error) {
p := filepath.Join(ifi.Name, "stationinfo.json") p := filepath.Join(ifi.Name, "stationinfo.json")
var info wifi.StationInfo var stations []*wifi.StationInfo
if err := s.unmarshalJSONFile(p, &info); err != nil { if err := s.unmarshalJSONFile(p, &stations); err != nil {
return nil, err return nil, err
} }
return &info, nil return stations, nil
} }

@ -45,8 +45,8 @@ func (c *Client) BSS(ifi *Interface) (*BSS, error) {
return c.c.BSS(ifi) return c.c.BSS(ifi)
} }
// StationInfo retrieves station statistics about a WiFi interface. // StationInfo retrieves all station statistics about a WiFi interface.
func (c *Client) StationInfo(ifi *Interface) (*StationInfo, error) { func (c *Client) StationInfo(ifi *Interface) ([]*StationInfo, error) {
return c.c.StationInfo(ifi) return c.c.StationInfo(ifi)
} }
@ -55,5 +55,5 @@ type osClient interface {
Close() error Close() error
Interfaces() ([]*Interface, error) Interfaces() ([]*Interface, error)
BSS(ifi *Interface) (*BSS, error) BSS(ifi *Interface) (*BSS, error)
StationInfo(ifi *Interface) (*StationInfo, error) StationInfo(ifi *Interface) ([]*StationInfo, error)
} }

@ -18,7 +18,6 @@ import (
// Errors which may occur when interacting with generic netlink. // Errors which may occur when interacting with generic netlink.
var ( var (
errMultipleMessages = errors.New("expected only one generic netlink message")
errInvalidCommand = errors.New("invalid generic netlink response command") errInvalidCommand = errors.New("invalid generic netlink response command")
errInvalidFamilyVersion = errors.New("invalid generic netlink response family version") errInvalidFamilyVersion = errors.New("invalid generic netlink response family version")
) )
@ -120,9 +119,9 @@ func (c *client) BSS(ifi *Interface) (*BSS, error) {
return parseBSS(msgs) return parseBSS(msgs)
} }
// StationInfo requests that nl80211 return station info for the specified // StationInfo requests that nl80211 return all station info for the specified
// Interface. // Interface.
func (c *client) StationInfo(ifi *Interface) (*StationInfo, error) { func (c *client) StationInfo(ifi *Interface) ([]*StationInfo, error) {
b, err := netlink.MarshalAttributes(ifi.idAttrs()) b, err := netlink.MarshalAttributes(ifi.idAttrs())
if err != nil { if err != nil {
return nil, err return nil, err
@ -147,22 +146,25 @@ func (c *client) StationInfo(ifi *Interface) (*StationInfo, error) {
return nil, err return nil, err
} }
switch len(msgs) { if len(msgs) == 0 {
case 0:
return nil, os.ErrNotExist return nil, os.ErrNotExist
case 1:
break
default:
return nil, errMultipleMessages
} }
stations := make([]*StationInfo, len(msgs))
for i := range msgs {
if err := c.checkMessages(msgs, nl80211.CmdNewStation); err != nil { if err := c.checkMessages(msgs, nl80211.CmdNewStation); err != nil {
return nil, err return nil, err
} }
return parseStationInfo(msgs[0].Data) if stations[i], err = parseStationInfo(msgs[i].Data); err != nil {
return nil, err
}
}
return stations, nil
} }
// checkMessages verifies that response messages from generic netlink contain // checkMessages verifies that response messages from generic netlink contain
// the command and family version we expect. // the command and family version we expect.
func (c *client) checkMessages(msgs []genetlink.Message, command uint8) error { func (c *client) checkMessages(msgs []genetlink.Message, command uint8) error {
@ -323,25 +325,32 @@ func parseStationInfo(b []byte) (*StationInfo, error) {
return nil, err return nil, err
} }
var info StationInfo
for _, a := range attrs { for _, a := range attrs {
// The other attributes that are returned here appear to indicate the
// interface index and MAC address, which is information we already
// possess. No need to parse them for now.
if a.Type != nl80211.AttrStaInfo {
continue
}
switch a.Type {
case nl80211.AttrMac:
info.HardwareAddr = net.HardwareAddr(a.Data)
case nl80211.AttrStaInfo:
nattrs, err := netlink.UnmarshalAttributes(a.Data) nattrs, err := netlink.UnmarshalAttributes(a.Data)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var info StationInfo
if err := (&info).parseAttributes(nattrs); err != nil { if err := (&info).parseAttributes(nattrs); err != nil {
return nil, err return nil, err
} }
// nl80211.AttrStaInfo is last attibute we are interested in
return &info, nil return &info, nil
default:
// The other attributes that are returned here appear
// nl80211.AttrIfindex, nl80211.AttrGeneration
// No need to parse them for now.
continue
}
} }
// No station info found // No station info found

@ -28,6 +28,6 @@ func (c *client) BSS(ifi *Interface) (*BSS, error) {
} }
// StationInfo always returns an error. // StationInfo always returns an error.
func (c *client) StationInfo(ifi *Interface) (*StationInfo, error) { func (c *client) StationInfo(ifi *Interface) ([]*StationInfo, error) {
return nil, errUnimplemented return nil, errUnimplemented
} }

@ -128,6 +128,9 @@ type Interface struct {
// StationInfo contains statistics about a WiFi interface operating in // StationInfo contains statistics about a WiFi interface operating in
// station mode. // station mode.
type StationInfo struct { type StationInfo struct {
// The hardware address of the station.
HardwareAddr net.HardwareAddr
// The time since the station last connected. // The time since the station last connected.
Connected time.Duration Connected time.Duration

@ -103,10 +103,10 @@
"revisionTime": "2017-12-14T18:12:53Z" "revisionTime": "2017-12-14T18:12:53Z"
}, },
{ {
"checksumSHA1": "6HM95OVqE3M27obRphrlXgXIHNw=", "checksumSHA1": "Y7cjrOeOvA/ic+B8WCp2JyLEuvs=",
"path": "github.com/mdlayher/wifi", "path": "github.com/mdlayher/wifi",
"revision": "ebeb58da4bc660b4882176fedcd10015f44af89c", "revision": "9a2549315201616119128afe421d1601ef3506f9",
"revisionTime": "2018-06-01T12:43:32Z" "revisionTime": "2018-06-15T12:49:15Z"
}, },
{ {
"checksumSHA1": "VzutdH69PUqRqhrDVv6F91ebQd4=", "checksumSHA1": "VzutdH69PUqRqhrDVv6F91ebQd4=",

Loading…
Cancel
Save