A parsing library for RIPE Atlas measurement results
RIPE Atlas generates a lot of data, and the format of that data changes over
time. Often you want to do something simple like fetch the median RTT for each
measurement result between date X
and date Y
. Unfortunately, there are are
dozens of edge cases to account for while parsing the JSON, like the format of
errors and firmware upgrades that changed the format entirely.
To make this easier for our users (and for ourselves), we wrote an easy to use parser that's smart enough to figure out the best course of action for each result, and return to you a useful, native Python object.
You can parse a result in a few ways. You can just pass the JSON-encoded string:
from ripe.atlas.sagan import PingResult
my_result = PingResult("<result string from RIPE Atlas ping measurement>")
print(my_result.rtt_median)
123.456
print(my_result.af)
6
You can do the JSON-decoding yourself:
from ripe.atlas.sagan import PingResult
my_result = PingResult(
json.dumps("<result string from RIPE Atlas ping measurement>")
)
print(my_result.rtt_median)
123.456
print(my_result.af)
6
You can let the parser guess the right type for you, though this incurs a small performance penalty:
from ripe.atlas.sagan import Result
my_result = Result.get("<result string from RIPE Atlas ping measurement>")
print(my_result.rtt_median)
123.456
print(my_result.af)
6
Every result type has its own properties, with a few common between all types.
Specifically, these attributes exist on all *Result
objects:
created
An arrow object (like datetime, but better) of thetimestamp
fieldmeasurement_id
probe_id
firmware
An integer representing the firmware versionorigin
Thefrom
attribute in the resultis_error
Set toTrue
if an error was found
Additionally, each of the result types have their own properties, like
packet_size
, responses
, certificates
, etc. You can take a look at the
classes themselves, or just look at the tests if you're curious. But to get you
started, here are some examples:
# Ping
ping_result.packets_sent # Int
ping_result.rtt_median # Float, rounded to 3 decimal places
ping_result.rtt_average # Float, rounded to 3 decimal places
# Traceroute
traceroute_result.af # 4 or 6
traceroute_result.total_hops # Int
traceroute_result.destination_address # An IP address string
# DNS
dns_result.responses # A list of Response objects
dns_result.responses[0].response_time # Float, rounded to 3 decimal places
dns_result.responses[0].headers # A list of Header objects
dns_result.responses[0].headers[0].nscount # The NSCOUNT value for the first header
dns_result.responses[0].questions # A list of Question objects
dns_result.responses[0].questions[0].type # The TYPE value for the first question
dns_result.responses[0].abuf # The raw, unparsed abuf string
# SSL Certificates
ssl_result.af # 4 or 6
ssl_result.certificates # A list of Certificate objects
ssl_result.certificates[0].checksum # The checksum for the first certificate
# HTTP
http_result.af # 4 or 6
http_result.uri # A URL string
http_result.responses # A list of Response objects
http_result.responses[0].body_size # The size of the body of the first response
If you'd like Sagan to be less forgiving, you only need to pass
on_error=Result.ACTION_FAIL
when you're instantiating your object. To use one
of our previous examples:
from ripe.atlas.sagan import Result
my_result = Result.get(
'{"dnserr":"non-recoverable failure in name resolution",...}',
on_error=Result.ACTION_FAIL
)
The above will explode with a ResultParseError
.
Essentially, we tried to support everything. If you pass in a DNS result
string, the parser will return a DNSResult
object, which contains a list of
Response
s, each with an abuf
property, as well as all of the information in
that abuf: header, question, answer, etc.
from ripe.atlas.sagan import DnsResult
my_dns_result = DnsResult("<result string from a RIPE Atlas DNS measurement>")
my_dns_result.responses[0].abuf # The entire string
my_dns_result.responses[0].abuf.header.arcount # Decoded from the abuf
We do the same sort of thing for SSL measurements, traceroutes, everything. We try to save you the effort of sorting through whatever is in the result.
As you might have guessed, with all of this magic going on under the hood, there are a few dependencies:
- arrow
- pyOpenSSL (Optional: see "Troubleshooting" below)
- python-dateutil
- pytz
- IPy
Additionally, we recommend that you also install ujson as it will speed up the JSON-decoding step considerably, and sphinx if you intend to build the documentation files for offline use.
The stable version should always be in PyPi, so you can install it with pip
:
$ pip install ripe.atlas.sagan
Some setups (like MacOS) have trouble with building the dependencies required
for reading SSL certificates. If you don't care about SSL stuff and only want
to use sagan to say, parse traceroute or DNS results, then you can tell the
installer to skip building pyOpenSSL
by doing the following:
$ SAGAN_WITHOUT_SSL=1 pip install ripe.atlas.sagan
Complete documentation can always be found on
the RIPE Atlas project page, and if you're
not online, the project itself contains a docs
directory -- everything you
should need is in there.
But why Sagan? The RIPE Atlas team decided to name all of its modules after explorers, and what better name for a parser than that of the man who spent decades reaching out to the public about the wonders of the cosmos?
- 0.2.6
- Replaced
SslResult.get_checksum_chain()
with theSslResult.checksum_chain
property. - Added support for catching results with an
err
property as an actual error.
- Replaced
- 0.2.5
- Fixed a bug in how the
on_error
andon_malformation
preferences weren't being passed down into the subcomponents of the results.
- Fixed a bug in how the
- 0.2.4
- Support for
seconds_since_sync
across all measurement types
- Support for
- 0.2.3
- "Treat a missing Type value in a DNS result as a malformation" (Issue #36)
- 0.2.2
- Minor bugfixes
- 0.2.1
- Added a
median_rtt
value to tracerouteHop
objects. - Smarter and more consistent error handling in traceroute and HTTP results.
- Added an
error_message
property to all objects that is set toNone
by default.
- Added a
- 0.2.0
- Totally reworked error and malformation handling. We now differentiate
between a result (or portion thereof) being malformed (and therefore
unparsable) and simply containing an error such as a timeout. Look for
an
is_error
property or anis_malformed
property on every object to check for it, or simply passon_malformation=Result.ACTION_FAIL
if you'd prefer things to explode with an exception. See the documentation for more details - Added lazy-loading features for parsing abuf and qbuf values out of DNS results.
- Removed the deprecated properties from
dns.Response
. You must now access values likeedns0
fromdns.Response.abuf.edns0
. - More edge cases have been found and accommodated.
- Totally reworked error and malformation handling. We now differentiate
between a result (or portion thereof) being malformed (and therefore
unparsable) and simply containing an error such as a timeout. Look for
an
- 0.1.15
- 0.1.14
- Fixed the deprecation warnings in
DnsResult
to point to the right place.
- Fixed the deprecation warnings in
- 0.1.13
- Better handling of
DNSResult
errors - Rearranged the way abufs were handled in the
DnsResult
class to make way forqbuf
values as well. The old method of accessingheader
,answers
,questions
, etc is still available viaResponse
, but this will go away when we move to 0.2. Deprecation warnings are in place.
- Better handling of
- 0.1.12
- Smarter code for checking whether the target was reached in
TracerouteResults
. - We now handle the
destination_option_size
andhop_by_hop_option_size
values inTracerouteResult
. - Extended support for ICMP header info in traceroute
Hop
class by introducing a newIcmpHeader
class.
- Smarter code for checking whether the target was reached in
- 0.1.8
- Broader support for SSL checksums. We now make use of
md5
andsha1
, as well as the originalsha256
.
- Broader support for SSL checksums. We now make use of