Use & Examples¶
The library contains a full test suite for each measurement type, so if you’re looking for examples, it’s a good idea to start there. For this document we’ll cover basic usage and some simple examples to get you started.
How To Use This Library¶
Sagan’s sole purpose is to make RIPE Atlas measurements manageable from within Python. You shouldn’t have to be fiddling with JSON, or trying to find values that changed locations between firmware versions. Instead, you should always be able to pass in the JSON string and immediately get usable Python objects.
The one thing that tends to confuse people when first trying out Sagan is that this library operates on single measurement results, and not a list of results. If you have a list of results (for example, the output of the measurement results API), then you must loop over those results and pass each result to Sagan for parsing.
To that end, the interface is pretty simple. If you have a ping measurement result, then use the PingResult class to make use of the data:
from ripe.atlas.sagan import PingResult my_result = PingResult('this is where your big JSON blob goes') my_result.af # Returns 6 my_result.rtt_median # Returns 123.456
rtt_median isn’t actually in the JSON data passed in. It’s
calculated during the parsing phase so you don’t need to fiddle with looping
over attributes in a list and doing the math yourself.
Plain Text Not Required¶
It should be noted that while all of the examples here use a plain text string
for our results, Sagan doesn’t force you to pass in a string. It’s just as
happy with a Python dict, the result of already running your result string
import json from ripe.atlas.sagan import PingResult my_result_dict = json.loads('this is where your big JSON blob goes') my_result = PingResult(my_result_dict) my_result.af # Returns 6 my_result.rtt_median # Returns 123.456
There may be a case where you have code that’s just expected to parse a result
string, without knowing ahead of time what type of result it is. For this we
make use of the parent
from ripe.atlas.sagan import Result my_result = Result.get('this is where your big JSON blob goes') my_result.af # Returns 6 my_result.rtt_median # Returns 123.456
As you can see it works just like PingResult, but doesn’t force you to know its type up front. Note that this does incur a small performance penalty however.
Errors & Malformations¶
RIPE Atlas, like the Internet is never 100% what you’d expect. Sometimes your measurement will return an error such as a timout or DNS lookup problem, and sometimes the data in a result might even be malformed on account of data corruption, damaged probe storage, etc.
And like the most applications on the Internet, Sagan attemps to handle these inconsistencies gracefully. You can decide just how gracefully however.
Say for example you’ve got a result that looks alright, but the
is damaged in some way rendering it unreadable. You’ll find that while the
DnsResult object will not have a
is_malformed=False, the portion that is
unreadable will be set to
from ripe.atlas.sagan import DnsResult my_result = DnsResult('your JSON blob') my_result.is_error # False my_result.is_malformed # False my_result.responses.abuf.is_malformed # True my_result.responses.abuf.is_malformed # False
You can control what you’d like Sagan to do in these cases by setting
on_malformation= when parsing:
from ripe.atlas.sagan import DnsResult # Sets is_malformed=True and issues a warning my_result = DnsResult('your JSON blob') # Sets is_malformed=True my_result = DnsResult('your JSON blob', on_malformation=DnsResult.ACTION_IGNORE) # Sets explodes with a ResultParseError my_result = DnsResult('your JSON blob', on_malformation=DnsResult.ACTION_FAIL)
Similarly, you can do the same thing with
on_error=, which perform the same
way when Sagan encounters an error like a timeout or DNS lookup problem.
Error handling is not yet complete in Sagan, so if you run across a case where it behaves in a way other than what you’d expect, please send a copy of the problematic result to email@example.com and we’ll use it to update this library.
Parsing Results out of a Local File¶
Assume for a moment that you’ve downloaded a bunch of results into a local file
using our fragmented JSON format. That is, you have in your possession a file
that has a separate JSON result on every line. For the purposes of our example
we’ll call it
from ripe.atlas.sagan import Result my_results_file = "/path/to/file.txt" with open(my_results_file) as results: for result in results.readlines(): parsed_result = Result.get(result) print(parsed_result.origin)
Basically you use Python to open the file (using
with) and then loop over
each line in the file (
.readlines()), sending each line into Sagan which
parsed_result. With that result, you can then pull out any of
the values you like, using the Attributes & Methods documentation as a
Pulling Directly from the API¶
A common use case for the parser is to plug it into our RESTful API service. The process for this is pretty simple: fetch a bunch of results, loop over them, and for each one, apply the parser to get the value you want.
Say for example you want to get the
checksum value for each result from
measurement #1012449. To do this, we’ll fetch the latest results from each
probe via the
measurement-latest API, and parse each one to get the
import requests from ripe.atlas.sagan import SslResult source = "https://atlas.ripe.net/api/v1/measurement-latest/1012449/" response = requests.get(source).json for probe_id, result in response.items(): result = result # There's only one result for each probe parsed_result = SslResult(result) # Parsing magic! # Each SslResult has n certificates for certificate in parsed_result.certificates: print(certificate.checksum) # Print the checksum for this certificate # Make use of the handy get_checksum_chain() to render the checksum of each certificate into one string if you want print(parsed_result.get_checksum_chain())
Samples from Each Type¶
For more information regarding all properties available, you should consult the Ping section of this documentation.:
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
For more information regarding all properties available, you should consult the Traceroute section of this documentation.:
traceroute_result.af # 4 or 6 traceroute_result.total_hops # Int traceroute_result.destination_address # An IP address string
For more information regarding all properties available, you should consult the DNS section of this documentation.:
dns_result.responses # A list of Response objects dns_result.responses.response_time # Float, rounded to 3 decimal places dns_result.responses.headers # A list of Header objects dns_result.responses.headers.nscount # The NSCOUNT value for the first header dns_result.responses.questions # A list of Question objects dns_result.responses.questions.type # The TYPE value for the first question dns_result.responses.abuf # The raw, unparsed abuf string
For more information regarding all properties available, you should consult the SSL Certificate section of this documentation.:
ssl_result.af # 4 or 6 ssl_result.certificates # A list of Certificate objects ssl_result.certificates.checksum # The checksum for the first certificate
For more information regarding all properties available, you should consult the HTTP section of this documentation.:
http_result.af # 4 or 6 http_result.uri # A URL string http_result.responses # A list of Response objects http_result.responses.body_size # The size of the body of the first response