Differential Analysis
Differential Analysis provides the ability to compare and understand the changes between two Live Query runs. The differential is calculated based on point-in-time snapshots. These features answer the question, “What changed on endpoints, and when?”.
Overview
This guide follows the steps for comparing two “point-in-time snapshots” of endpoints using a few different options and downloading the results using the Differential object. This example aims to understand what Firefox add-ons were added or removed between the two Live Query snapshot intervals.
1. Prerequisites
To perform a Differential Analysis, create the “point-in-time” snapshots of your endpoints with Live Query or use existing ones. You can find a step-by-step Live Query API guide here and a version for the CBC Python SDK here. The example Live Query runs look for added or removed Firefox add-ons.
2. Query Comparison
Start a Query Comparison with the ID’s you received from step 1. If the supplied newer_run_id
is from a recurring Live Query run,
the older_run_id
is not required - the backend will automatically compare it to previous to the supplied one.
The backend will throw a specific error if you provide a query id from a single Live Query run.
You can read more about it here.
Query Comparison
Basic Query
This example shows the basic result of the Differential
object. The .newer_run_id()
method is required - it accepts the
run id that you want to mark as the starting point-in-time snapshot. By default, only the number of changes between the two runs are returned.
To receive the actual differential data, use the .count_only()
method, as featured in the Actual Changes example.
>>> from cbc_sdk import CBCloudAPI
>>> from cbc_sdk.audit_remediation import Differential
>>>
>>> cb = CBCloudAPI(profile='sample')
>>>
>>> query = cb.select(Differential).newer_run_id('jcdqsju4utpaayj5dh5r2llzffeolg0u').older_run_id('yhbg3wcea9y1l4asiltky5tupkgauzas')
>>> run = query.submit()
>>> print(run)
Differential object, bound to https://defense-dev01.cbdtest.io.
-------------------------------------------------------------------------------
diff_processed_time: 0.037
diff_results: [list:1 item]:
[0]: {'device_id': 11412673, 'change_count': 19, 'ad...
newer_run_create_time: 2022-10-19T13:29:34.429Z
newer_run_id: n6cv24lh3pnh4zbciotahl82tm4tsuo7
newer_run_not_responded_devices: [list:1 item]:
[0]: 17331059
older_run_create_time: 2022-10-19T13:19:49.812Z
older_run_id: olquodvqz8kekxug2o2jsxcdnltak9hu
older_run_not_responded_devices: [list:1 item]:
[0]: 17331059
You can also access a dictionary representation of the response with the .to_json()
method.
>>> print(run.to_json())
{'diff_processed_time': 0.037,
'diff_results': [{'added_count': 1,
'change_count': 1,
'changes': None,
'device_id': 12345,
'newer_run_row_count': 21,
'older_run_row_count': 20,
'removed_count': 0}],
'newer_run_create_time': '2022-08-10T13:07:44.194Z',
'newer_run_id': 'jcdqsju4utpaayj5dh5r2llzffeolg0u',
'newer_run_not_responded_devices': [],
'older_run_create_time': '2022-08-10T12:57:03.872Z',
'older_run_id': 'yhbg3wcea9y1l4asiltky5tupkgauzas',
'older_run_not_responded_devices': []}
Actual Changes
Using the .count_only()
method with a value of False
will allow you to see the actual changes between the two snapshots.
To use this method, append it to the rest of the Differential object query. The actual changes will be in the changes
property, under diff_results
.
>>> from cbc_sdk import CBCloudAPI
>>> from cbc_sdk.audit_remediation import Differential
>>>
>>> cb = CBCloudAPI(profile='sample')
>>>
>>> query = cb.select(Differential).newer_run_id('jcdqsju4utpaayj5dh5r2llzffeolg0u').older_run_id('yhbg3wcea9y1l4asiltky5tupkgauzas').count_only(False)
>>> actual_changes = query.submit()
>>> print(actual_changes.diff_results)
[{'device_id': 11412673, 'change_count': 19, 'added_count': 19, 'removed_count': 0, 'changes': [{'action': 'ADDED', 'fields': [{'key': 'name', 'value': 'Visionary – Soft'}]}, {'action': 'ADDED', 'fields': [{'key': 'name', 'value': 'Activist – Balanced'}]}, {'action': 'ADDED', 'fields': [{'key': 'name', 'value': 'Visionary – Balanced'}]}, {'action': 'ADDED', 'fields': [{'key': 'name', 'value': 'Innovator – Soft'}]}, {'action': 'ADDED', 'fields': [{'key': 'name', 'value': 'Activist – Bold'}]}, {'action': 'ADDED', 'fields': [{'key': 'name', 'value': 'Dreamer – Soft'}]}, {'action': 'ADDED', 'fields': [{'key': 'name', 'value': 'Dreamer – Balanced'}]}, {'action': 'ADDED', 'fields': [{'key': 'name', 'value': 'Expressionist – Bold'}]}, {'action': 'ADDED', 'fields': [{'key': 'name', 'value': 'Innovator – Bold'}]}, {'action': 'ADDED', 'fields': [{'key': 'name', 'value': 'AdGuard AdBlocker'}]}, {'action': 'ADDED', 'fields': [{'key': 'name', 'value': 'Expressionist – Balanced'}]}, {'action': 'ADDED', 'fields': [{'key': 'name', 'value': 'Visionary – Bold'}]}, {'action': 'ADDED', 'fields': [{'key': 'name', 'value': 'Playmaker – Soft'}]}, {'action': 'ADDED', 'fields': [{'key': 'name', 'value': 'Innovator – Balanced'}]}, {'action': 'ADDED', 'fields': [{'key': 'name', 'value': 'Expressionist – Soft'}]}, {'action': 'ADDED', 'fields': [{'key': 'name', 'value': 'Playmaker – Balanced'}]}, {'action': 'ADDED', 'fields': [{'key': 'name', 'value': 'Playmaker – Bold'}]}, {'action': 'ADDED', 'fields': [{'key': 'name', 'value': 'Activist – Soft'}]}, {'action': 'ADDED', 'fields': [{'key': 'name', 'value': 'Dreamer – Bold'}]}], 'older_run_row_count': 26, 'newer_run_row_count': 45}]
In the example response you can see that 19 items were added between the two snapshot intervals.
Filter Devices
Using the .set_device_ids()
you can narrow down the query to a specific devices only. The method accepts an array of integers.
To use this method, append it to the rest of the Differential object query or combine it with any of the other methods.
>>> from cbc_sdk import CBCloudAPI
>>> from cbc_sdk.audit_remediation import Differential
>>>
>>> cb = CBCloudAPI(profile='sample')
>>>
>>> query = cb.select(Differential).newer_run_id('jcdqsju4utpaayj5dh5r2llzffeolg0u').older_run_id('yhbg3wcea9y1l4asiltky5tupkgauzas')
>>> actual_changes = query.count_only(False).set_device_ids([12345])
>>> run = actual_changes.submit()
>>> print(run.to_json())
{'diff_processed_time': 0.039,
'diff_results': [{'added_count': 1,
'change_count': 1,
'changes': [{'action': 'ADDED',
'fields': [{'key': 'name',
'value': 'AdBlocker Ultimate'}]}],
'device_id': 12345,
'newer_run_row_count': 21,
'older_run_row_count': 20,
'removed_count': 0}],
'newer_run_create_time': '2022-08-10T13:07:44.194Z',
'newer_run_id': 'jcdqsju4utpaayj5dh5r2llzffeolg0u',
'newer_run_not_responded_devices': [],
'older_run_create_time': '2022-08-10T12:57:03.872Z',
'older_run_id': 'yhbg3wcea9y1l4asiltky5tupkgauzas',
'older_run_not_responded_devices': []}
Export Results
Using the .async_export()
you can create an asynchronous job that exports the results from the run.
To use this method, append it to the rest of the Differential object query or combine it with any of the other methods.
>>> from cbc_sdk import CBCloudAPI
>>> from cbc_sdk.audit_remediation import Differential
>>>
>>> cb = CBCloudAPI(profile='sample')
>>>
>>> query = cb.select(Differential).newer_run_id('jcdqsju4utpaayj5dh5r2llzffeolg0u').older_run_id('yhbg3wcea9y1l4asiltky5tupkgauzas')
>>> export = query.count_only(False).set_device_ids([12345]).async_export()
>>> export.await_completion()
>>> # write the results to a file
>>> export.get_output_as_file("example_data.json")