├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── case-studies ├── CA-2083-hinted-handoff.ded ├── CA-2434-bootstrap-synchronization.ded ├── MR-2995-failed-after-expiry.ded ├── MR-3858-hadoop.ded ├── ZK-1270-racing-sent-flag.ded └── pb_asynchronous.ded ├── docker-compose.yml ├── faultinjectors ├── data-types.go └── molly.go ├── go.mod ├── go.sum ├── graphing ├── corrections.go ├── diagrams.go ├── differential-provenance.go ├── extensions.go ├── hazard-analysis.go ├── helpers.go ├── pre-post-prov.go ├── preprocessing.go └── prototype.go ├── main.go └── report ├── assets ├── index.html ├── vendor │ ├── bootstrap.min.css │ ├── bootstrap.min.js │ ├── d3.min.js │ ├── fontawesome-all.min.css │ ├── jquery.min.js │ └── nemo.css └── webfonts │ ├── fa-brands-400.eot │ ├── fa-brands-400.svg │ ├── fa-brands-400.ttf │ ├── fa-brands-400.woff │ ├── fa-brands-400.woff2 │ ├── fa-regular-400.eot │ ├── fa-regular-400.svg │ ├── fa-regular-400.ttf │ ├── fa-regular-400.woff │ ├── fa-regular-400.woff2 │ ├── fa-solid-900.eot │ ├── fa-solid-900.svg │ ├── fa-solid-900.ttf │ ├── fa-solid-900.woff │ └── fa-solid-900.woff2 ├── helpers.go └── webpage.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | 16 | # Project-specific 17 | nemo 18 | results/ 19 | tmp/ 20 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM neo4j:3.3.3 2 | 3 | RUN wget -O /var/lib/neo4j/plugins/apoc-3.3.0.2-all.jar https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases/download/3.3.0.2/apoc-3.3.0.2-all.jar 4 | 5 | EXPOSE 7474 7473 7687 6 | 7 | CMD ["neo4j"] 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean reset deps build 2 | 3 | all: clean build 4 | 5 | clean: 6 | go clean -i ./... 7 | sudo rm -rf tmp/* 8 | 9 | reset: 10 | go clean -i ./... 11 | sudo docker-compose down 12 | sudo rm -rf tmp/* 13 | sudo rm -rf results/* 14 | 15 | build: 16 | CGO_ENABLED=0 go build -ldflags '-extldflags "-static"' 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nemo 2 | 3 | Nemo debugs Distributed Systems by analyzing provenance graphs obtained during fault injection. 4 | 5 | 6 | ## Protocol Case Studies 7 | 8 | [Here](https://github.com/numbleroot/nemo/tree/master/case-studies) we provide the Dedalus code for the six case studies we performed as part of the evaluation of our [CIDR 2019 paper](https://people.ucsc.edu/~palvaro/p122-oldenburg-cidr19.pdf). 9 | 10 | 11 | ## Running Nemo 12 | 13 | We require two things for running Nemo: a [Go](https://golang.org/doc/install) and a [Docker](https://docs.docker.com/install/overview/) installation. Preferably, both come from your system's package manager, if available. Make sure to start the Docker daemon afterwards. 14 | 15 | Once, these two dependencies are installed and properly configured, run: 16 | ``` 17 | user@system $ git clone git@github.com:numbleroot/nemo.git ${GOPATH}/src/github.com/numbleroot/nemo 18 | ``` 19 | 20 | To build and run auxiliary components, execute the following commands: 21 | ``` 22 | user@system $ cd ${GOPATH}/src/github.com/numbleroot/nemo 23 | user@system $ sudo docker-compose -f docker-compose.yml build 24 | user@system $ sudo docker-compose -f docker-compose.yml up -d 25 | user@system $ make build 26 | ``` 27 | This should take care of preparing the environment and building the Nemo executable. Verify via `sudo docker-compose -f docker-compose.yml ps` that the Neo4J container defined in [docker-compose.yml](https://github.com/numbleroot/nemo/blob/master/docker-compose.yml) is running correctly. 28 | 29 | Finally, having run Molly (see [below](https://github.com/numbleroot/nemo#integrating-with-molly)) on the target distributed system prior to the following action and Molly successfully identifying a bug, run Nemo as follows: 30 | ``` 31 | user@system $ ./nemo -faultInjOut 32 | ``` 33 | 34 | Nemo should debug the Molly execution now. If all goes well, you will be referred to a prepared webpage report to open in your browser. 35 | 36 | 37 | ### Integrating with Molly 38 | 39 | In case you rely on [Molly](https://github.com/palvaro/molly) for finding bugs (as we did in our CIDR paper), we require a slightly modified set of output files and format. Please check out the following fork: [Molly fork](https://github.com/KamalaRamas/molly/tree/graphing) (Kamala's fork of Molly set to latest commit on branch `graphing`). 40 | -------------------------------------------------------------------------------- /case-studies/CA-2083-hinted-handoff.ded: -------------------------------------------------------------------------------- 1 | // CA-2083, Racing schema and data message. 2 | // sbt "run-main edu.berkeley.cs.boom.molly.SyncFTChecker --nodes n1,n2 --EOT 6 --EFF 4 --crashes 0 --prov-diagrams src/test/resources/examples_ft/nemo/CA-2083-hinted-handoff.ded" 3 | 4 | 5 | // Logic. 6 | 7 | schema_msg(N2, N1, S)@async :- begin_hh(N1, N2, S, _); 8 | hh_step2(N1, N2, D)@next :- begin_hh(N1, N2, _, D); 9 | data_msg(N2, N1, D)@async :- hh_step2(N1, N2, D); 10 | 11 | schema(N2, N1, S) :- schema_msg(N2, N1, S); 12 | schema(N2, N1, S)@next :- schema(N2, N1, S); 13 | 14 | complete(N2, N1, S, D) :- data_msg(N2, N1, D), schema(N2, N1, S); 15 | complete(N2, N1, S, D)@next :- complete(N2, N1, S, D); 16 | 17 | got_data(N2, D) :- data_msg(N2, _, D); 18 | got_data(N2, D)@next :- got_data(N2, D); 19 | 20 | 21 | // Correctness specification. 22 | 23 | pre(D) :- got_data(N2, D); 24 | post(D) :- complete(_, _, _, D); 25 | 26 | 27 | // Init. 28 | 29 | begin_hh("n1", "n2", "schema", "data")@1; 30 | -------------------------------------------------------------------------------- /case-studies/CA-2434-bootstrap-synchronization.ded: -------------------------------------------------------------------------------- 1 | // CA-2434 2 | // sbt "run-main edu.berkeley.cs.boom.molly.SyncFTChecker --nodes n1,n2,n3,n4 --EOT 7 --EFF 5 --crashes 1 --prov-diagrams src/test/resources/examples_ft/nemo/CA-2434-bootstrap-synchronization.ded" 3 | 4 | 5 | // Logic. 6 | 7 | data(Node, Data)@next :- data(Node, Data); 8 | data(Joiner, Data)@next :- join_rsp(Joiner, _, Data); 9 | 10 | timerr(Joiner, 0) :- do_join(Joiner); 11 | timerr(J, N+1)@next :- timerr(J, N); 12 | 13 | join(Anchor, Joiner)@async :- do_join(Joiner), primary(Joiner, Anchor); 14 | join(Anchor2, Joiner)@async :- timerr(Joiner, 2), secondary(Joiner, Anchor2), notin join_rsp(Joiner, _, _); 15 | 16 | join_rsp(Joiner, Anchor, Data)@async :- join(Anchor, Joiner), data(Anchor, Data); 17 | join_rsp(J, A, D)@next :- join_rsp(J, A, D); 18 | 19 | primary(J, A)@next :- primary(J, A); 20 | secondary(J, A)@next :- secondary(J, A); 21 | 22 | votes(Data, count) :- data(Node, Data), notin crash(Node, Node, _); 23 | 24 | 25 | // Correctness specification. 26 | 27 | pre(Data) :- data(Node, Data), Data == "new"; 28 | post(Data) :- data(_, Data), votes(Data, Cnt), Cnt > 1; 29 | 30 | 31 | // Init. 32 | 33 | data("n1", "new")@1; 34 | data("n2", "new")@1; 35 | data("n3", "old")@1; 36 | primary("n4", "n2")@1; 37 | secondary("n4", "n3")@1; 38 | do_join("n4")@2; 39 | -------------------------------------------------------------------------------- /case-studies/MR-2995-failed-after-expiry.ded: -------------------------------------------------------------------------------- 1 | // MR-2995 2 | // sbt "run-main edu.berkeley.cs.boom.molly.SyncFTChecker --nodes rm,nm,am --EOT 8 --EFF 4 --crashes 1 --prov-diagrams src/test/resources/examples_ft/nemo/MR-2995-failed-after-expiry.ded" 3 | 4 | 5 | // Logic. 6 | 7 | container(Nm, Rm, X)@async :- begin(Rm, Nm, _, X); 8 | container(Nm, Rm, X)@next :- container(Nm, Rm, X); 9 | 10 | timerr(Rm, Nm, Am, X, 0) :- begin(Rm, Nm, Am, X); 11 | timerr(Rm, Nm, Am, X, N+1)@next :- timerr(Rm, Nm, Am, X, N); 12 | 13 | initialize(Nm, Am)@async :- init(Am, Nm); 14 | initialize(Nm, Am)@next :- initialize(Nm, Am); 15 | 16 | done(Am, Nm, X)@async :- initialize(Nm, Am), container(Nm, _, X); 17 | 18 | buffer_done(Am, Nm, X) :- done(Am, Nm, X); 19 | buffer_done(Am, Nm, X)@next :- buffer_done(Am, Nm, X); 20 | 21 | expiry(Am, Rm, X)@async :- timerr(Rm, Nm, Am, X, 4); 22 | expiry(Am, Rm, X)@next :- expiry(Am, Rm, X); 23 | 24 | 25 | // Correctness specification. 26 | 27 | pre(Am) :- initialize(Nm, Am); 28 | post(Am) :- buffer_done(Am, _, _); 29 | 30 | 31 | // Init. 32 | 33 | begin("rm", "nm", "am", 1)@1; 34 | init("am", "nm")@2; 35 | 36 | -------------------------------------------------------------------------------- /case-studies/MR-3858-hadoop.ded: -------------------------------------------------------------------------------- 1 | // MR-3858, Incorrect local logic when job result is committed from multiple workers to manager. 2 | // sbt "run-main edu.berkeley.cs.boom.molly.SyncFTChecker --nodes am,w1,w2 --EOT 8 --EFF 4 --crashes 1 --prov-diagrams --negative-support src/test/resources/examples_ft/nemo/MR-3858-hadoop.ded" 3 | 4 | 5 | // Logic. 6 | 7 | am(W, A)@next :- am(W, A); 8 | 9 | can_commit(Am, Task, Worker)@async :- task_attempt(Worker, Task), am(Worker, Am); 10 | 11 | ccs(A, T, W) :- can_commit(A, T, W); 12 | ccs(A, T, W)@next :- ccs(A, T, W); 13 | ccc(A, T, count) :- ccs(A, T, W); 14 | 15 | commit(Am, Task, Worker) :- can_commit(Am, Task, Worker), ccc(Am, Task, C), C == 1; 16 | ok(Worker, Task)@async :- commit(Am, Task, Worker); 17 | no(Worker, Task)@async :- can_commit(Am, Task, Worker), ccc(Am, Task, C), C > 1; 18 | 19 | committed(Am, Task)@next :- commit(Am, Task, _); 20 | committed(Am, T)@next :- committed(Am, T); 21 | 22 | do_work(W, T)@next :- ok(W, T); 23 | done_commit(Am, T, W)@async :- do_work(W, T), am(W, Am); 24 | 25 | done(Am, T) :- done_commit(Am, T, _); 26 | done(A, T)@next :- done(A, T); 27 | 28 | 29 | // Correctness specification. 30 | 31 | pre(T) :- committed(Am, T), notin crash(Am, Am, _); 32 | post(T) :- done(_, T); 33 | 34 | 35 | // Init. 36 | 37 | am("w1", "am")@1; 38 | am("w2", "am")@1; 39 | 40 | task_attempt("w1", "task1")@1; 41 | task_attempt("w2", "task1")@4; 42 | task_attempt("w2", "task1")@5; 43 | -------------------------------------------------------------------------------- /case-studies/ZK-1270-racing-sent-flag.ded: -------------------------------------------------------------------------------- 1 | // ZK-1270, Setting local sent flag races with remote acknowledgement. 2 | // sbt "run-main edu.berkeley.cs.boom.molly.SyncFTChecker --nodes FF,LL,A --EOT 6 --EFF 3 --crashes 0 --prov-diagrams src/test/resources/examples_ft/nemo/ZK-1270-racing-sent-flag.ded" 3 | 4 | 5 | // Logic. 6 | 7 | newleader(F, L, Round)@async :- elected(L, Round), ff(L, F); 8 | timerr(L, R, 0) :- elected(L, R); 9 | timerr(L, R, C+1)@next :- timerr(L, R, C); 10 | 11 | sent_flag(L, R)@next :- timerr(L, R, C), C > 1; 12 | ff(L, F)@next :- ff(L, F); 13 | 14 | attest(F, A, C)@async :- attestor(A, F, C); 15 | attest(F, A, C)@next :- attest(F, A, C); 16 | attestor(A, F, C+1)@next :- attestor(A, F, C); 17 | attestations(F, count) :- attest(F, _, C); 18 | 19 | defer(F, L, Round)@next :- newleader(F, L, Round), attestations(F, N), N > 1; 20 | ack(L, F, Round)@async :- newleader(F, L, Round), attestations(F, 1); 21 | ack(L, F, Round)@async :- defer(F, L, Round); 22 | 23 | acked(L, R) :- ack(L, _, R); 24 | acked(L, R)@next :- acked(L, R); 25 | 26 | end_proto(L, F, R) :- ack(L, F, R), sent_flag(L, R); 27 | end_proto(L, F, R)@next :- end_proto(L, F, R); 28 | 29 | 30 | // Correctness specification. 31 | 32 | pre(L, R) :- acked(L, R); 33 | post(L, R) :- end_proto(L, _, R); 34 | 35 | 36 | // Init. 37 | 38 | attestor("A", "FF", 1)@1; 39 | ff("LL", "FF")@1; 40 | elected("LL", 1)@2; 41 | -------------------------------------------------------------------------------- /case-studies/pb_asynchronous.ded: -------------------------------------------------------------------------------- 1 | // Asynchronous Primary/Backup Replication. 2 | // sbt "run-main edu.berkeley.cs.boom.molly.SyncFTChecker --EOT 6 --EFF 4 --crashes 1 --prov-diagrams --negative-support --nodes C,a,b,c src/test/resources/examples_ft/nemo/pb_asynchronous.ded" 3 | 4 | 5 | // Network relationships. 6 | 7 | network("a", "a")@1; 8 | network("a", "b")@1; 9 | network("a", "c")@1; 10 | network("b", "a")@1; 11 | network("b", "b")@1; 12 | network("b", "c")@1; 13 | network("c", "a")@1; 14 | network("c", "b")@1; 15 | network("c", "c")@1; 16 | network(Node, Other)@next :- network(Node, Other); 17 | 18 | primary("a", "a")@1; 19 | primary(Node, Prim)@next :- primary(Node, Prim); 20 | 21 | replica("a", "b")@1; 22 | replica("a", "c")@1; 23 | replica(Prim, Rep)@next :- replica(Prim, Rep); 24 | 25 | client("C", "C")@1; 26 | client(Cli, Cli)@next :- client(Cli, Cli); 27 | 28 | conn_out("C", "a")@1; 29 | conn_out("a", "C")@1; 30 | conn_out(Cli, Prim)@next :- conn_out(Cli, Prim); 31 | 32 | 33 | 34 | // Trigger start. 35 | begin("C", "foo")@1; 36 | 37 | 38 | // Protocol behavior. 39 | 40 | // Client sends request to primary. 41 | request(Prim, Pload, Cli)@async :- begin(Cli, Pload), conn_out(Cli, Prim); 42 | 43 | // Primary immediately acks to client. Asynchronous Primary/Backup. 44 | ack(Cli, Prim, Pload)@async :- request(Prim, Pload, Cli); 45 | acked(Cli, Prim, Pload) :- ack(Cli, Prim, Pload); 46 | acked(Cli, Prim, Pload)@next :- acked(Cli, Prim, Pload); 47 | 48 | // Primary replicates to replicas in background. 49 | replicate(Rep, Pload, Prim, Cli)@async :- request(Prim, Pload, Cli), replica(Prim, Rep); 50 | 51 | // Nodes in the network persist received requests. 52 | log(Prim, Pload) :- request(Prim, Pload, Cli); 53 | log(Rep, Pload) :- replicate(Rep, Pload, _, _); 54 | log(Rep, Pload)@next :- log(Rep, Pload); 55 | 56 | 57 | 58 | // Invariants. 59 | 60 | // E2E property: Any time we ACK a payload to a client, we expect 61 | // that payload to be persisted on all correct replicas. 62 | pre(Pload) :- acked(Cli, Prim, Pload); 63 | post(Pload) :- log(Node, Pload), primary(Prim, Prim), notin crash(Node, Node, _), Node != Prim; 64 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | 5 | graphdb: 6 | image: "numbleroot/neo4j-apoc:3.3.3" 7 | build: "./" 8 | container_name: "graphdb" 9 | hostname: "graphdb" 10 | ports: 11 | - "127.0.0.1:7474:7474" 12 | - "127.0.0.1:7687:7687" 13 | volumes: 14 | - "./tmp:/data" 15 | environment: 16 | - "NEO4J_AUTH=none" 17 | - "NEO4J_dbms_memory_pagecache_size=8192m" 18 | - "NEO4J_dbms_memory_heap_max__size=8192m" 19 | - "NEO4J_dbms_memory_heap_initial__size=8192m" 20 | - "NEO4J_dbms_security_procedures_unrestricted=apoc.*" 21 | - "NEO4J_apoc_export_file_enabled=true" 22 | - "NEO4J_apoc_import_file_enabled=true" 23 | network_mode: "bridge" 24 | ulimits: 25 | nproc: 65535 26 | nofile: 27 | soft: 65535 28 | hard: 65535 29 | -------------------------------------------------------------------------------- /faultinjectors/data-types.go: -------------------------------------------------------------------------------- 1 | package faultinjectors 2 | 3 | // Structs. 4 | 5 | // CrashFailure 6 | type CrashFailure struct { 7 | Node string `json:"node"` 8 | Time uint `json:"time"` 9 | } 10 | 11 | // MessageLoss 12 | type MessageLoss struct { 13 | From string `json:"from"` 14 | To string `json:"to"` 15 | Time uint `json:"time"` 16 | } 17 | 18 | // FailureSpec 19 | type FailureSpec struct { 20 | EOT uint `json:"eot"` 21 | EFF uint `json:"eff"` 22 | MaxCrashes uint `json:"maxCrashes"` 23 | Nodes *[]string `json:"nodes"` 24 | Crashes *[]CrashFailure `json:"crashes"` 25 | Omissions *[]MessageLoss `json:"omissions"` 26 | } 27 | 28 | // Model 29 | type Model struct { 30 | Tables map[string][][]string `json:"tables"` 31 | } 32 | 33 | // Message 34 | type Message struct { 35 | Content string `json:"table"` 36 | SendNode string `json:"from"` 37 | RecvNode string `json:"to"` 38 | SendTime uint `json:"sendTime"` 39 | RecvTime uint `json:"receiveTime"` 40 | } 41 | 42 | // Goal 43 | type Goal struct { 44 | ID string `json:"id"` 45 | Label string `json:"label"` 46 | Table string `json:"table"` 47 | Time string `json:"time"` 48 | CondHolds bool `json:"conditionHolds,omitempty"` 49 | Sender string `json:"sender,omitempty"` 50 | Receiver string `json:"receiver,omitempty"` 51 | } 52 | 53 | // Rule 54 | type Rule struct { 55 | ID string `json:"id"` 56 | Label string `json:"label"` 57 | Table string `json:"table"` 58 | Type string `json:"type"` 59 | } 60 | 61 | // Edge 62 | type Edge struct { 63 | From string `json:"from"` 64 | To string `json:"to"` 65 | } 66 | 67 | // ProvData 68 | type ProvData struct { 69 | Goals []Goal `json:"goals"` 70 | Rules []Rule `json:"rules"` 71 | Edges []Edge `json:"edges"` 72 | } 73 | 74 | // Missing 75 | type Missing struct { 76 | Rule *Rule 77 | Goals []*Goal 78 | } 79 | 80 | // Run 81 | type Run struct { 82 | Iteration uint `json:"iteration"` 83 | Status string `json:"status"` 84 | FailureSpec *FailureSpec `json:"failureSpec"` 85 | Model *Model `json:"model"` 86 | Messages []*Message `json:"messages"` 87 | PreProv *ProvData `json:"preProv,omitempty"` 88 | TimePreHolds map[string]bool `json:"timePreHolds,omitempty"` 89 | PostProv *ProvData `json:"postProv,omitempty"` 90 | TimePostHolds map[string]bool `json:"timePostHolds,omitempty"` 91 | Recommendation []string `json:"recommendation,omitempty"` 92 | Corrections []string `json:"corrections,omitempty"` 93 | MissingEvents []*Missing `json:"missingEvents,omitempty"` 94 | InterProto []string `json:"interProto,omitempty"` 95 | InterProtoMissing []string `json:"interProtoMissing,omitempty"` 96 | UnionProto []string `json:"unionProto,omitempty"` 97 | UnionProtoMissing []string `json:"unionProtoMissing,omitempty"` 98 | } 99 | 100 | // Molly 101 | type Molly struct { 102 | Run string 103 | OutputDir string 104 | Runs []*Run 105 | RunsIters []uint 106 | SuccessRunsIters []uint 107 | FailedRunsIters []uint 108 | } 109 | -------------------------------------------------------------------------------- /faultinjectors/molly.go: -------------------------------------------------------------------------------- 1 | package faultinjectors 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | 7 | "encoding/json" 8 | "io/ioutil" 9 | "path/filepath" 10 | ) 11 | 12 | // Functions. 13 | 14 | // LoadOutput 15 | func (m *Molly) LoadOutput() error { 16 | 17 | // Find out how many iterations the fault injection run contains. 18 | rawRunsCont, err := ioutil.ReadFile(filepath.Join(m.OutputDir, "runs.json")) 19 | if err != nil { 20 | return fmt.Errorf("Could not read runs.json file in faultInjOut directory: %v", err) 21 | } 22 | 23 | // Read runs.json file into structure defined above. 24 | err = json.Unmarshal(rawRunsCont, &m.Runs) 25 | if err != nil { 26 | return fmt.Errorf("Failed to unmarshal JSON content to runs structure: %v\n", err) 27 | } 28 | 29 | m.RunsIters = make([]uint, len(m.Runs)) 30 | m.SuccessRunsIters = make([]uint, 0, len(m.Runs)) 31 | m.FailedRunsIters = make([]uint, 0, 3) 32 | 33 | // Load antecedent and consequent provenance for each iteration. 34 | for i := range m.Runs { 35 | 36 | // Create lookup map for when the 37 | // antecedent holds in this run. 38 | m.Runs[i].TimePreHolds = make(map[string]bool) 39 | for _, table := range m.Runs[i].Model.Tables["pre"] { 40 | m.Runs[i].TimePreHolds[table[(len(table)-1)]] = true 41 | } 42 | 43 | // Create lookup map for when the 44 | // consequent holds in this run. 45 | m.Runs[i].TimePostHolds = make(map[string]bool) 46 | for _, table := range m.Runs[i].Model.Tables["post"] { 47 | m.Runs[i].TimePostHolds[table[(len(table)-1)]] = true 48 | } 49 | 50 | // Note return status of fault injection 51 | // run in separate structure. 52 | m.RunsIters[i] = m.Runs[i].Iteration 53 | if m.Runs[i].Status == "success" { 54 | m.SuccessRunsIters = append(m.SuccessRunsIters, m.Runs[i].Iteration) 55 | } else { 56 | m.FailedRunsIters = append(m.FailedRunsIters, m.Runs[i].Iteration) 57 | } 58 | 59 | preProvFile := filepath.Join(m.OutputDir, fmt.Sprintf("run_%d_pre_provenance.json", i)) 60 | postProvFile := filepath.Join(m.OutputDir, fmt.Sprintf("run_%d_post_provenance.json", i)) 61 | 62 | rawPreProvCont, err := ioutil.ReadFile(preProvFile) 63 | if err != nil { 64 | return fmt.Errorf("Failed reading antecedent provenance of file '%v': %v", preProvFile, err) 65 | } 66 | 67 | err = json.Unmarshal(rawPreProvCont, &m.Runs[i].PreProv) 68 | if err != nil { 69 | return fmt.Errorf("Failed to unmarshal JSON antecedent provenance data: %v\n", err) 70 | } 71 | 72 | for j := range m.Runs[i].PreProv.Goals { 73 | 74 | if m.Runs[i].PreProv.Goals[j].Table == "clock" { 75 | 76 | clkTimeWildRegex := regexp.MustCompile(`, ([\d]+), __WILDCARD__\)`) 77 | clkTimeWildMatches := clkTimeWildRegex.FindStringSubmatch(m.Runs[i].PreProv.Goals[j].Label) 78 | 79 | clkTimeTwoRegex := regexp.MustCompile(`, ([\d]+), ([\d]+)\)`) 80 | clkTimeTwoMatches := clkTimeTwoRegex.FindStringSubmatch(m.Runs[i].PreProv.Goals[j].Label) 81 | 82 | if len(clkTimeWildMatches) > 0 { 83 | m.Runs[i].PreProv.Goals[j].Time = clkTimeWildMatches[1] 84 | } 85 | 86 | if len(clkTimeTwoMatches) > 0 { 87 | m.Runs[i].PreProv.Goals[j].Time = clkTimeTwoMatches[1] 88 | } 89 | } 90 | 91 | // Prefix goals with "pre_". 92 | m.Runs[i].PreProv.Goals[j].ID = fmt.Sprintf("run_%d_pre_%s", m.Runs[i].Iteration, m.Runs[i].PreProv.Goals[j].ID) 93 | 94 | // Tentative mark as antecedent not yet achieved 95 | // until we can do graph operations on this provenance. 96 | m.Runs[i].PreProv.Goals[j].CondHolds = false 97 | } 98 | 99 | // Prefix rules with "pre_". 100 | for j := range m.Runs[i].PreProv.Rules { 101 | m.Runs[i].PreProv.Rules[j].ID = fmt.Sprintf("run_%d_pre_%s", m.Runs[i].Iteration, m.Runs[i].PreProv.Rules[j].ID) 102 | } 103 | 104 | // Prefix edges with "pre_". 105 | for j := range m.Runs[i].PreProv.Edges { 106 | m.Runs[i].PreProv.Edges[j].From = fmt.Sprintf("run_%d_pre_%s", m.Runs[i].Iteration, m.Runs[i].PreProv.Edges[j].From) 107 | m.Runs[i].PreProv.Edges[j].To = fmt.Sprintf("run_%d_pre_%s", m.Runs[i].Iteration, m.Runs[i].PreProv.Edges[j].To) 108 | } 109 | 110 | rawPostProvCont, err := ioutil.ReadFile(postProvFile) 111 | if err != nil { 112 | return fmt.Errorf("Failed reading consequent provenance of file '%v': %v", postProvFile, err) 113 | } 114 | 115 | err = json.Unmarshal(rawPostProvCont, &m.Runs[i].PostProv) 116 | if err != nil { 117 | return fmt.Errorf("Failed to unmarshal JSON consequent provenance data: %v\n", err) 118 | } 119 | 120 | for j := range m.Runs[i].PostProv.Goals { 121 | 122 | if m.Runs[i].PostProv.Goals[j].Table == "clock" { 123 | 124 | clkTimeWildRegex := regexp.MustCompile(`, ([\d]+), __WILDCARD__\)`) 125 | clkTimeWildMatches := clkTimeWildRegex.FindStringSubmatch(m.Runs[i].PostProv.Goals[j].Label) 126 | 127 | clkTimeTwoRegex := regexp.MustCompile(`, ([\d]+), ([\d]+)\)`) 128 | clkTimeTwoMatches := clkTimeTwoRegex.FindStringSubmatch(m.Runs[i].PostProv.Goals[j].Label) 129 | 130 | if len(clkTimeWildMatches) > 0 { 131 | m.Runs[i].PostProv.Goals[j].Time = clkTimeWildMatches[1] 132 | } 133 | 134 | if len(clkTimeTwoMatches) > 0 { 135 | m.Runs[i].PostProv.Goals[j].Time = clkTimeTwoMatches[1] 136 | } 137 | } 138 | 139 | // Prefix goals with "post_". 140 | m.Runs[i].PostProv.Goals[j].ID = fmt.Sprintf("run_%d_post_%s", m.Runs[i].Iteration, m.Runs[i].PostProv.Goals[j].ID) 141 | 142 | // Tentative mark as consequent not yet achieved 143 | // until we can do graph operations on this provenance. 144 | m.Runs[i].PostProv.Goals[j].CondHolds = false 145 | } 146 | 147 | // Prefix rules with "post_". 148 | for j := range m.Runs[i].PostProv.Rules { 149 | m.Runs[i].PostProv.Rules[j].ID = fmt.Sprintf("run_%d_post_%s", m.Runs[i].Iteration, m.Runs[i].PostProv.Rules[j].ID) 150 | } 151 | 152 | // Prefix edges with "post_". 153 | for j := range m.Runs[i].PostProv.Edges { 154 | m.Runs[i].PostProv.Edges[j].From = fmt.Sprintf("run_%d_post_%s", m.Runs[i].Iteration, m.Runs[i].PostProv.Edges[j].From) 155 | m.Runs[i].PostProv.Edges[j].To = fmt.Sprintf("run_%d_post_%s", m.Runs[i].Iteration, m.Runs[i].PostProv.Edges[j].To) 156 | } 157 | 158 | // Prepare slice for recommendations. 159 | m.Runs[i].Recommendation = make([]string, 0, 5) 160 | } 161 | 162 | return nil 163 | } 164 | 165 | // GetFailureSpec returns the failure specification of this analysis. 166 | func (m *Molly) GetFailureSpec() *FailureSpec { 167 | return m.Runs[0].FailureSpec 168 | } 169 | 170 | // GetMsgsFailedRuns returns the messages sent from all failed runs. 171 | func (m *Molly) GetMsgsFailedRuns() [][]*Message { 172 | 173 | msgs := make([][]*Message, len(m.FailedRunsIters)) 174 | for i := range m.FailedRunsIters { 175 | msgs[i] = make([]*Message, len(m.Runs[m.FailedRunsIters[i]].Messages)) 176 | msgs[i] = m.Runs[m.FailedRunsIters[i]].Messages 177 | } 178 | 179 | return msgs 180 | } 181 | 182 | // GetOutput returns all parsed runs from Molly. 183 | func (m *Molly) GetOutput() []*Run { 184 | return m.Runs 185 | } 186 | 187 | // GetRunsIters returns the iteration numbers 188 | // of all runs known in this struct. 189 | func (m *Molly) GetRunsIters() []uint { 190 | return m.RunsIters 191 | } 192 | 193 | // GetSuccessRunsIters returns indexes of successful runs. 194 | func (m *Molly) GetSuccessRunsIters() []uint { 195 | return m.SuccessRunsIters 196 | } 197 | 198 | // GetFailedRunsIters returns indexes of failed runs. 199 | func (m *Molly) GetFailedRunsIters() []uint { 200 | return m.FailedRunsIters 201 | } 202 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/numbleroot/nemo 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/awalterschulze/gographviz v0.0.0-20180131143711-c45f112108a6 7 | github.com/johnnadratowski/golang-neo4j-bolt-driver v0.0.0-20171218143611-1108d6e66ccf 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/awalterschulze/gographviz v0.0.0-20180131143711-c45f112108a6 h1:YnkzcmzLAWNRVF0QzzuQNTy8xPJUgC5Y+yMJ2g2JieQ= 2 | github.com/awalterschulze/gographviz v0.0.0-20180131143711-c45f112108a6/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs= 3 | github.com/johnnadratowski/golang-neo4j-bolt-driver v0.0.0-20171218143611-1108d6e66ccf h1:MdIjqQxAkwca9be8XGc941HRNjnhFMuBhmsliaEYvQ0= 4 | github.com/johnnadratowski/golang-neo4j-bolt-driver v0.0.0-20171218143611-1108d6e66ccf/go.mod h1:xwUw3ZE1/D9drQgpluhRs4peTMKm1tQEZ4p7DrpyqwE= 5 | -------------------------------------------------------------------------------- /graphing/corrections.go: -------------------------------------------------------------------------------- 1 | package graphing 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | 8 | graph "github.com/johnnadratowski/golang-neo4j-bolt-driver/structures/graph" 9 | fi "github.com/numbleroot/nemo/faultinjectors" 10 | ) 11 | 12 | // Structs. 13 | 14 | // GoalRulePair 15 | type GoalRulePair struct { 16 | Goal *fi.Goal 17 | Rule *fi.Rule 18 | } 19 | 20 | // Functions. 21 | 22 | // findPreTriggers extracts the trigger events 23 | // that mark the transition from the antecedent 24 | // turning from false to true. 25 | func (n *Neo4J) findPreTriggers(run uint) (map[*fi.Rule][]*GoalRulePair, error) { 26 | 27 | // Query antecedent provenance of specified run 28 | // for event chains representing the following form: 29 | // aggregation rule, trigger goal, trigger rule. 30 | stmtTriggers, err := n.Conn1.PrepareNeo(` 31 | MATCH (a:Rule {run: {run}, condition: "pre"})-[*1]->(g:Goal {run: {run}, condition: "pre", condition_holds: false})-[*1]->(r:Rule {run: {run}, condition: "pre"}) 32 | WHERE (:Goal {run: {run}, condition: "pre", condition_holds: true})-[*1]->(a)-[*1]->(g)-[*1]->(r) 33 | RETURN a AS aggregation, g AS goal, r AS rule; 34 | `) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | triggersRaw, err := stmtTriggers.QueryNeo(map[string]interface{}{ 40 | "run": run, 41 | }) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | // Prepare a map indexed by aggregation rule, 47 | // collecting all trigger goals and rules. 48 | triggers := make(map[*fi.Rule][]*GoalRulePair) 49 | 50 | for err == nil { 51 | 52 | var trigger []interface{} 53 | 54 | trigger, _, err = triggersRaw.NextNeo() 55 | if err != nil && err != io.EOF { 56 | return nil, err 57 | } else if err == nil { 58 | 59 | // Type-assert the nodes into well-defined struct. 60 | agg := trigger[0].(graph.Node) 61 | goal := trigger[1].(graph.Node) 62 | rule := trigger[2].(graph.Node) 63 | 64 | // Parse parts that make up label of goal. 65 | goalLabel := strings.TrimLeft(goal.Properties["label"].(string), goal.Properties["table"].(string)) 66 | goalLabel = strings.Trim(goalLabel, "()") 67 | goalLabelParts := strings.Split(goalLabel, ", ") 68 | 69 | aggregation := &fi.Rule{ 70 | ID: agg.Properties["id"].(string), 71 | Label: agg.Properties["label"].(string), 72 | Table: agg.Properties["table"].(string), 73 | Type: agg.Properties["type"].(string), 74 | } 75 | 76 | if len(triggers[aggregation]) < 1 { 77 | triggers[aggregation] = make([]*GoalRulePair, 0, 4) 78 | } 79 | 80 | // Insert goal-rule pair into slice indexed 81 | // by aggregation rule. 82 | triggers[aggregation] = append(triggers[aggregation], &GoalRulePair{ 83 | Goal: &fi.Goal{ 84 | ID: goal.Properties["id"].(string), 85 | Label: goal.Properties["label"].(string), 86 | Table: goal.Properties["table"].(string), 87 | Time: goal.Properties["time"].(string), 88 | CondHolds: goal.Properties["condition_holds"].(bool), 89 | Receiver: goalLabelParts[0], 90 | }, 91 | Rule: &fi.Rule{ 92 | ID: rule.Properties["id"].(string), 93 | Label: rule.Properties["label"].(string), 94 | Table: rule.Properties["table"].(string), 95 | Type: rule.Properties["type"].(string), 96 | }, 97 | }) 98 | } 99 | } 100 | 101 | err = triggersRaw.Close() 102 | if err != nil { 103 | return nil, err 104 | } 105 | 106 | err = stmtTriggers.Close() 107 | if err != nil { 108 | return nil, err 109 | } 110 | 111 | return triggers, nil 112 | } 113 | 114 | // findPostTriggers extracts the trigger events 115 | // that mark the transition from the consequent 116 | // turning from false to true. 117 | func (n *Neo4J) findPostTriggers(run uint) (map[*fi.Goal][]*fi.Rule, error) { 118 | 119 | // Query consequent provenance of specified run 120 | // for pairs of trigger goal and trigger rule. 121 | stmtTriggers, err := n.Conn1.PrepareNeo(` 122 | MATCH (g:Goal {run: {run}, condition: "post", condition_holds: true})-[*1]->(r:Rule {run: {run}, condition: "post"}) 123 | WHERE (:Rule {run: {run}, condition: "post"})-[*1]->(g)-[*1]->(r)-[*1]->(:Goal {run: {run}, condition: "post", condition_holds: false})-[*1]->(:Rule {run: {run}, condition: "post"}) 124 | RETURN g AS goal, r AS rule; 125 | `) 126 | if err != nil { 127 | return nil, err 128 | } 129 | 130 | triggersRaw, err := stmtTriggers.QueryNeo(map[string]interface{}{ 131 | "run": run, 132 | }) 133 | if err != nil { 134 | return nil, err 135 | } 136 | 137 | // Prepare a map indexed by trigger goal, 138 | // collecting all trigger rules. 139 | triggers := make(map[*fi.Goal][]*fi.Rule) 140 | 141 | for err == nil { 142 | 143 | var trigger []interface{} 144 | 145 | trigger, _, err = triggersRaw.NextNeo() 146 | if err != nil && err != io.EOF { 147 | return nil, err 148 | } else if err == nil { 149 | 150 | // Type-assert the nodes into well-defined struct. 151 | goal := trigger[0].(graph.Node) 152 | rule := trigger[1].(graph.Node) 153 | 154 | // Parse parts that make up label of goal. 155 | goalLabel := strings.TrimLeft(goal.Properties["label"].(string), goal.Properties["table"].(string)) 156 | goalLabel = strings.Trim(goalLabel, "()") 157 | goalLabelParts := strings.Split(goalLabel, ", ") 158 | 159 | g := &fi.Goal{ 160 | ID: goal.Properties["id"].(string), 161 | Label: goal.Properties["label"].(string), 162 | Table: goal.Properties["table"].(string), 163 | Time: goal.Properties["time"].(string), 164 | CondHolds: goal.Properties["condition_holds"].(bool), 165 | Receiver: goalLabelParts[0], 166 | } 167 | 168 | if len(triggers[g]) < 1 { 169 | triggers[g] = make([]*fi.Rule, 0, 3) 170 | } 171 | 172 | // Insert rule into slice indexed by goal. 173 | triggers[g] = append(triggers[g], &fi.Rule{ 174 | ID: rule.Properties["id"].(string), 175 | Label: rule.Properties["label"].(string), 176 | Table: rule.Properties["table"].(string), 177 | Type: rule.Properties["type"].(string), 178 | }) 179 | } 180 | } 181 | 182 | err = triggersRaw.Close() 183 | if err != nil { 184 | return nil, err 185 | } 186 | 187 | err = stmtTriggers.Close() 188 | if err != nil { 189 | return nil, err 190 | } 191 | 192 | return triggers, nil 193 | } 194 | 195 | // GenerateCorrections extracts the triggering events required 196 | // to achieve antecedent and consequent in the first (successful) 197 | // run. We use this information in case the fault injector was 198 | // able to inject a fault that caused the invariant to be violated 199 | // in order to generate correction suggestions for how the system 200 | // designers could strengthen the antecedent to only fire when 201 | // we are sure the consequent holds as well. 202 | func (n *Neo4J) GenerateCorrections() ([]string, error) { 203 | 204 | fmt.Printf("Running generation of suggestions for corrections (pre ~> post)... ") 205 | 206 | // Recs will contain our top-level recommendations. 207 | recs := make([]string, 0, 6) 208 | 209 | // Extract the antecedent trigger event chains. 210 | preTriggers, err := n.findPreTriggers(0) 211 | if err != nil { 212 | return nil, err 213 | } 214 | 215 | // Extract the consequent trigger event chains. 216 | postTriggers, err := n.findPostTriggers(0) 217 | if err != nil { 218 | return nil, err 219 | } 220 | 221 | // Prepare slice of strings representing the 222 | // compound of trigger rules required for firing 223 | // the respective aggregation rule. 224 | preTriggerRules := make(map[string]string) 225 | 226 | // Track per pre-rule if the nodes involved on 227 | // both sides, pre and post, differ. If so, we 228 | // have to take extra steps. 229 | differentNodes := make(map[string]map[string][]*fi.Goal) 230 | 231 | for preAgg := range preTriggers { 232 | 233 | differentNodes[preAgg.Table] = make(map[string][]*fi.Goal) 234 | 235 | for i := range preTriggers[preAgg] { 236 | 237 | if preTriggerRules[preAgg.Table] == "" { 238 | preTriggerRules[preAgg.Table] = fmt.Sprintf("%s(%s, ...) :- %s(%s, ...)", preAgg.Table, preTriggers[preAgg][i].Goal.Receiver, preTriggers[preAgg][i].Rule.Table, preTriggers[preAgg][i].Goal.Receiver) 239 | } else { 240 | preTriggerRules[preAgg.Table] = fmt.Sprintf("%s, %s(%s, ...)", preTriggerRules[preAgg.Table], preTriggers[preAgg][i].Rule.Table, preTriggers[preAgg][i].Goal.Receiver) 241 | } 242 | } 243 | } 244 | 245 | for preAgg := range preTriggers { 246 | 247 | for i := range preTriggers[preAgg] { 248 | 249 | for postGoal := range postTriggers { 250 | 251 | if preTriggers[preAgg][i].Goal.Receiver != postGoal.Receiver { 252 | 253 | if differentNodes[preAgg.Table][preTriggers[preAgg][i].Goal.Receiver] == nil { 254 | differentNodes[preAgg.Table][preTriggers[preAgg][i].Goal.Receiver] = make([]*fi.Goal, 0, 3) 255 | } 256 | 257 | differentNodes[preAgg.Table][preTriggers[preAgg][i].Goal.Receiver] = append(differentNodes[preAgg.Table][preTriggers[preAgg][i].Goal.Receiver], postGoal) 258 | } 259 | } 260 | } 261 | 262 | aggNew := preTriggerRules[preAgg.Table] 263 | 264 | if len(differentNodes[preAgg.Table]) == 0 { 265 | 266 | // The involved nodes for this antecedent 267 | // rule and all consequent rules to add are 268 | // the same ones. Thus, local order suffices. 269 | 270 | for postGoal := range postTriggers { 271 | aggNew = fmt.Sprintf("%s, %s(%s, ...)", aggNew, postGoal.Table, postGoal.Receiver) 272 | } 273 | } else { 274 | 275 | // At least one goal on the consequent side 276 | // takes place on a different node than this 277 | // antecedent's goal. We need communication. 278 | 279 | for pre := range differentNodes[preAgg.Table] { 280 | 281 | for post := range differentNodes[preAgg.Table][pre] { 282 | 283 | preNode := pre 284 | postNode := differentNodes[preAgg.Table][pre][post].Receiver 285 | postRule := differentNodes[preAgg.Table][pre][post].Table 286 | 287 | // Add the recommendation to integrate a message round 288 | // so that the receiver node in pre knows about the state. 289 | recs = append(recs, fmt.Sprintf("%s needs to know that %s has executed %s. Add:
        ack_%s(%s, ...)@async :- %s(%s, ...), ...;", preNode, postNode, postRule, postRule, preNode, postRule, postNode)) 290 | 291 | // Also, add receipt of this message as dependency to 292 | // the updated antecedent trigger. 293 | aggNew = fmt.Sprintf("%s, ack_%s(%s, sender=%s, ...)", aggNew, postRule, preNode, postNode) 294 | } 295 | } 296 | 297 | for i := range preTriggers[preAgg] { 298 | 299 | if preTriggers[preAgg][i].Rule.Type != "next" { 300 | 301 | // In case one of the rules underneath the aggregation rule 302 | // right above the triggering rules for the antecedent is 303 | // not of type next (i.e., state-preserving), we need to 304 | // introduce a buffering scheme so that we do not lose the 305 | // state required for firing pre. 306 | 307 | rule := preTriggers[preAgg][i].Rule.Table 308 | node := preTriggers[preAgg][i].Goal.Receiver 309 | 310 | // Add the buffer_RULE construct as a suggestion. 311 | recs = append(recs, fmt.Sprintf("Antecedent depends on timing of an onetime event. Make it persistent. Add:
        buffer_%s(%s, ...) :- %s(%s, ...), ...;
        buffer_%s(%s, ...)@next :- buffer_%s(%s, ...), ...;", rule, node, rule, node, rule, node, rule, node)) 312 | 313 | // Update the new antecedent trigger dependencies 314 | // by replacing the old rule with the new buffer_ rule. 315 | aggNew = strings.Replace(aggNew, fmt.Sprintf("%s(%s, ...)", rule, node), fmt.Sprintf("buffer_%s(%s, ...)", rule, node), -1) 316 | } 317 | } 318 | } 319 | 320 | // Finally, append the updated dependency rule 321 | // for firing the antecedent to our recommendations. 322 | recs = append(recs, fmt.Sprintf("Change: %s;     %s;", preTriggerRules[preAgg.Table], aggNew)) 323 | } 324 | 325 | fmt.Printf("done\n\n") 326 | 327 | return recs, nil 328 | } 329 | -------------------------------------------------------------------------------- /graphing/diagrams.go: -------------------------------------------------------------------------------- 1 | package graphing 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/awalterschulze/gographviz" 8 | graph "github.com/johnnadratowski/golang-neo4j-bolt-driver/structures/graph" 9 | fi "github.com/numbleroot/nemo/faultinjectors" 10 | ) 11 | 12 | // Functions. 13 | 14 | // createDOT 15 | func createDOT(edges []graph.Path, graphType string) (*gographviz.Graph, error) { 16 | 17 | dotGraph := gographviz.NewGraph() 18 | 19 | // Name the DOT graph. 20 | err := dotGraph.SetName("dataflow") 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | // It is a directed graph. 26 | err = dotGraph.SetDir(true) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | // Make sure the background is transparent. 32 | err = dotGraph.AddNode("dataflow", "graph", map[string]string{ 33 | "bgcolor": "\"transparent\"", 34 | }) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | for i := range edges { 40 | 41 | from := edges[i].Nodes[0].Properties["id"].(string) 42 | to := edges[i].Nodes[1].Properties["id"].(string) 43 | 44 | fromAttrs := make(map[string]string) 45 | 46 | fromAttrs["label"] = fmt.Sprintf("\"%s\"", edges[i].Nodes[0].Properties["label"]) 47 | fromAttrs["style"] = "\"filled, solid\"" 48 | fromAttrs["color"] = "\"black\"" 49 | fromAttrs["fontcolor"] = "\"black\"" 50 | fromAttrs["fillcolor"] = "\"white\"" 51 | 52 | // Style node differently based on time notion. 53 | if edges[i].Nodes[0].Properties["type"] == "async" { 54 | fromAttrs["style"] = "\"filled, bold\"" 55 | fromAttrs["color"] = "\"lawngreen\"" 56 | } else if edges[i].Nodes[0].Properties["type"] == "next" { 57 | fromAttrs["fontcolor"] = "\"gold\"" 58 | } 59 | 60 | // Style node differently based on achieved condition. 61 | if (edges[i].Nodes[0].Properties["condition_holds"] == true) && (graphType == "pre") { 62 | fromAttrs["color"] = "\"firebrick\"" 63 | fromAttrs["fillcolor"] = "\"firebrick\"" 64 | } else if (edges[i].Nodes[0].Properties["condition_holds"] == true) && (graphType == "post") { 65 | fromAttrs["color"] = "\"deepskyblue\"" 66 | fromAttrs["fillcolor"] = "\"deepskyblue\"" 67 | } 68 | 69 | // Alter shape based on being rule or goal. 70 | if edges[i].Nodes[0].Labels[0] == "Rule" { 71 | fromAttrs["shape"] = "rect" 72 | } else { 73 | fromAttrs["shape"] = "ellipse" 74 | } 75 | 76 | toAttrs := make(map[string]string) 77 | 78 | toAttrs["label"] = fmt.Sprintf("\"%s\"", edges[i].Nodes[1].Properties["label"]) 79 | toAttrs["style"] = "\"filled, solid\"" 80 | toAttrs["color"] = "\"black\"" 81 | toAttrs["fontcolor"] = "\"black\"" 82 | toAttrs["fillcolor"] = "\"white\"" 83 | 84 | // Style node differently based on time notion. 85 | if edges[i].Nodes[1].Properties["type"] == "async" { 86 | toAttrs["style"] = "\"filled, bold\"" 87 | toAttrs["color"] = "\"lawngreen\"" 88 | } else if edges[i].Nodes[1].Properties["type"] == "next" { 89 | toAttrs["fontcolor"] = "\"gold\"" 90 | } 91 | 92 | // Style node differently based on achieved condition. 93 | if (edges[i].Nodes[1].Properties["condition_holds"] == true) && (graphType == "pre") { 94 | toAttrs["color"] = "\"firebrick\"" 95 | toAttrs["fillcolor"] = "\"firebrick\"" 96 | } else if (edges[i].Nodes[1].Properties["condition_holds"] == true) && (graphType == "post") { 97 | toAttrs["color"] = "\"deepskyblue\"" 98 | toAttrs["fillcolor"] = "\"deepskyblue\"" 99 | } 100 | 101 | // Alter shape based on being rule or goal. 102 | if edges[i].Nodes[1].Labels[0] == "Rule" { 103 | toAttrs["shape"] = "rect" 104 | } else { 105 | toAttrs["shape"] = "ellipse" 106 | } 107 | 108 | // Add first node with all info from query. 109 | err := dotGraph.AddNode("dataflow", from, fromAttrs) 110 | if err != nil { 111 | return nil, err 112 | } 113 | 114 | // Add second node with all info from query. 115 | err = dotGraph.AddNode("dataflow", to, toAttrs) 116 | if err != nil { 117 | return nil, err 118 | } 119 | 120 | // Add edge to DOT graph. 121 | err = dotGraph.AddEdge(from, to, true, map[string]string{ 122 | "color": "\"black\"", 123 | }) 124 | if err != nil { 125 | return nil, err 126 | } 127 | } 128 | 129 | return dotGraph, nil 130 | } 131 | 132 | // createDiffDot 133 | func createDiffDot(diffRunID uint, diffEdges []graph.Path, failedRunID uint, failedEdges []graph.Path, successRunID uint, successPostProv *gographviz.Graph, missing []*fi.Missing) (*gographviz.Graph, *gographviz.Graph, error) { 134 | 135 | // Create map for lookup of missing events. 136 | missingMap := make(map[string]bool) 137 | for m := range missing { 138 | 139 | missingMap[missing[m].Rule.ID] = true 140 | for g := range missing[m].Goals { 141 | missingMap[missing[m].Goals[g].ID] = true 142 | } 143 | } 144 | 145 | diffDotGraph := gographviz.NewGraph() 146 | failedDotGraph := gographviz.NewGraph() 147 | 148 | // Name the DOT graphs. 149 | err := diffDotGraph.SetName("dataflow") 150 | if err != nil { 151 | return nil, nil, err 152 | } 153 | 154 | err = failedDotGraph.SetName("dataflow") 155 | if err != nil { 156 | return nil, nil, err 157 | } 158 | 159 | // They both are directed graph. 160 | err = diffDotGraph.SetDir(true) 161 | if err != nil { 162 | return nil, nil, err 163 | } 164 | 165 | err = failedDotGraph.SetDir(true) 166 | if err != nil { 167 | return nil, nil, err 168 | } 169 | 170 | // Make sure both backgrounds are transparent. 171 | err = diffDotGraph.AddNode("dataflow", "graph", map[string]string{ 172 | "bgcolor": "\"transparent\"", 173 | }) 174 | if err != nil { 175 | return nil, nil, err 176 | } 177 | 178 | err = failedDotGraph.AddNode("dataflow", "graph", map[string]string{ 179 | "bgcolor": "\"transparent\"", 180 | }) 181 | if err != nil { 182 | return nil, nil, err 183 | } 184 | 185 | for _, edge := range successPostProv.Edges.Edges { 186 | 187 | diffSrc := strings.Replace(edge.Src, fmt.Sprintf("run_%d", successRunID), fmt.Sprintf("run_%d", diffRunID), -1) 188 | diffDst := strings.Replace(edge.Dst, fmt.Sprintf("run_%d", successRunID), fmt.Sprintf("run_%d", diffRunID), -1) 189 | 190 | // Copy attribute map. 191 | attrMap := make(map[string]string) 192 | for j := range edge.Attrs { 193 | attrMap[string(j)] = edge.Attrs[j] 194 | } 195 | 196 | // Overwrite style attribute to hide edge. 197 | attrMap["style"] = "\"invis\"" 198 | 199 | // Copy the edge over to new graphs. 200 | err := diffDotGraph.AddEdge(diffSrc, diffDst, edge.Dir, attrMap) 201 | if err != nil { 202 | return nil, nil, err 203 | } 204 | 205 | err = failedDotGraph.AddEdge(diffSrc, diffDst, edge.Dir, attrMap) 206 | if err != nil { 207 | return nil, nil, err 208 | } 209 | } 210 | 211 | for _, node := range successPostProv.Nodes.Nodes { 212 | 213 | diffName := strings.Replace(node.Name, fmt.Sprintf("run_%d", successRunID), fmt.Sprintf("run_%d", diffRunID), -1) 214 | 215 | // Copy attribute map. 216 | attrMap := make(map[string]string) 217 | for j := range node.Attrs { 218 | attrMap[string(j)] = node.Attrs[j] 219 | } 220 | 221 | // Overwrite style attribute to hide node. 222 | attrMap["style"] = "\"invis\"" 223 | 224 | // Copy the node over to new graphs. 225 | err := diffDotGraph.AddNode("dataflow", diffName, attrMap) 226 | if err != nil { 227 | return nil, nil, err 228 | } 229 | 230 | err = failedDotGraph.AddNode("dataflow", diffName, attrMap) 231 | if err != nil { 232 | return nil, nil, err 233 | } 234 | } 235 | 236 | for i := range diffEdges { 237 | 238 | from := diffEdges[i].Nodes[0].Properties["id"].(string) 239 | to := diffEdges[i].Nodes[1].Properties["id"].(string) 240 | 241 | // Make nodes visible again that are 242 | // part of the selected subgraph. 243 | diffDotGraph.Nodes.Lookup[from].Attrs["style"] = "\"filled, solid\"" 244 | diffDotGraph.Nodes.Lookup[to].Attrs["style"] = "\"filled, solid\"" 245 | 246 | // Make edges visible again that are 247 | // part of the selected subgraph. 248 | for j := range diffDotGraph.Edges.SrcToDsts[from][to] { 249 | diffDotGraph.Edges.SrcToDsts[from][to][j].Attrs["style"] = "\"filled, solid\"" 250 | } 251 | 252 | // If one of the nodes is one of the 253 | // missing events, mark it specifically. 254 | _, isMissingFrom := missingMap[from] 255 | if isMissingFrom { 256 | diffDotGraph.Nodes.Lookup[from].Attrs["style"] = "\"filled, dashed, bold\"" 257 | diffDotGraph.Nodes.Lookup[from].Attrs["color"] = "\"mediumvioletred\"" 258 | } 259 | 260 | _, isMissingTo := missingMap[to] 261 | if isMissingTo { 262 | diffDotGraph.Nodes.Lookup[to].Attrs["style"] = "\"filled, dashed, bold\"" 263 | diffDotGraph.Nodes.Lookup[to].Attrs["color"] = "\"mediumvioletred\"" 264 | } 265 | } 266 | 267 | for i := range failedEdges { 268 | 269 | from := fmt.Sprintf("\"%s\"", failedEdges[i].Nodes[0].Properties["label"].(string)) 270 | to := fmt.Sprintf("\"%s\"", failedEdges[i].Nodes[1].Properties["label"].(string)) 271 | 272 | for j := range failedDotGraph.Nodes.Nodes { 273 | 274 | if (failedDotGraph.Nodes.Nodes[j].Attrs["label"] == from) || (failedDotGraph.Nodes.Nodes[j].Attrs["label"] == to) { 275 | failedDotGraph.Nodes.Nodes[j].Attrs["style"] = "\"filled, solid\"" 276 | } 277 | } 278 | } 279 | 280 | for i := range failedDotGraph.Edges.Edges { 281 | 282 | from := failedDotGraph.Edges.Edges[i].Src 283 | to := failedDotGraph.Edges.Edges[i].Dst 284 | 285 | if (failedDotGraph.Nodes.Lookup[from].Attrs["style"] == "\"filled, solid\"") && (failedDotGraph.Nodes.Lookup[to].Attrs["style"] == "\"filled, solid\"") { 286 | failedDotGraph.Edges.Edges[i].Attrs["style"] = "\"filled, solid\"" 287 | } 288 | } 289 | 290 | return diffDotGraph, failedDotGraph, err 291 | } 292 | -------------------------------------------------------------------------------- /graphing/differential-provenance.go: -------------------------------------------------------------------------------- 1 | package graphing 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | 8 | "os/exec" 9 | 10 | "github.com/awalterschulze/gographviz" 11 | graph "github.com/johnnadratowski/golang-neo4j-bolt-driver/structures/graph" 12 | fi "github.com/numbleroot/nemo/faultinjectors" 13 | ) 14 | 15 | // Functions. 16 | 17 | // CreateNaiveDiffProv 18 | func (n *Neo4J) CreateNaiveDiffProv(symmetric bool, failedRuns []uint, successPostProv *gographviz.Graph) ([]*gographviz.Graph, []*gographviz.Graph, [][]*fi.Missing, error) { 19 | 20 | fmt.Printf("Creating differential provenance (good - bad), naive way... ") 21 | 22 | exportQuery := `CALL apoc.export.cypher.query(" 23 | MATCH (failed:Goal {run: ###RUN###, condition: 'post'}) 24 | WITH collect(failed.label) AS failGoals 25 | 26 | MATCH pathSucc = (root:Goal {run: 0, condition: 'post'})-[*0..]->(goal:Goal {run: 0, condition: 'post'}) 27 | WHERE NOT root.label IN failGoals AND NOT goal.label IN failGoals 28 | RETURN pathSucc; 29 | ", "/tmp/export-differential-provenance", {format: "cypher-shell", cypherFormat: "create"}) 30 | YIELD time 31 | RETURN time; 32 | ` 33 | 34 | diffDots := make([]*gographviz.Graph, len(failedRuns)) 35 | failedDots := make([]*gographviz.Graph, len(failedRuns)) 36 | missingEvents := make([][]*fi.Missing, len(failedRuns)) 37 | 38 | for i := range failedRuns { 39 | 40 | diffRunID := 2000 + failedRuns[i] 41 | 42 | // Replace failed run in skeleton query. 43 | exportQuery = strings.Replace(exportQuery, "###RUN###", fmt.Sprintf("%d", failedRuns[i]), -1) 44 | _, err := n.Conn1.ExecNeo(exportQuery, nil) 45 | if err != nil { 46 | return nil, nil, nil, err 47 | } 48 | 49 | // Replace run ID part of node ID in saved queries. 50 | sedIDLong := fmt.Sprintf("s/`id`:\"run_0/`id`:\"run_%d/g", diffRunID) 51 | cmd := exec.Command("sudo", "docker", "exec", "graphdb", "sed", "-i", sedIDLong, "/tmp/export-differential-provenance") 52 | out, err := cmd.CombinedOutput() 53 | if err != nil { 54 | return nil, nil, nil, err 55 | } 56 | 57 | if strings.TrimSpace(string(out)) != "" { 58 | return nil, nil, nil, fmt.Errorf("Wrong return value from docker-compose exec sed diffprov run ID command: %s", out) 59 | } 60 | 61 | // Replace run ID in saved queries. 62 | sedIDShort := fmt.Sprintf("s/`run`:0/`run`:%d/g", diffRunID) 63 | cmd = exec.Command("sudo", "docker", "exec", "graphdb", "sed", "-i", sedIDShort, "/tmp/export-differential-provenance") 64 | out, err = cmd.CombinedOutput() 65 | if err != nil { 66 | return nil, nil, nil, err 67 | } 68 | 69 | if strings.TrimSpace(string(out)) != "" { 70 | return nil, nil, nil, fmt.Errorf("Wrong return value from docker-compose exec sed diffprov run ID command: %s", out) 71 | } 72 | 73 | // Import modified difference graph as new one. 74 | _, err = n.Conn1.ExecNeo(` 75 | CALL apoc.cypher.runFile("/tmp/export-differential-provenance", {statistics: false}); 76 | `, nil) 77 | if err != nil { 78 | return nil, nil, nil, err 79 | } 80 | 81 | // Query differential provenance graph for leaves. 82 | stmtLeaves, err := n.Conn1.PrepareNeo(` 83 | MATCH path = (root:Goal {run: {run}, condition: "post"})-[*0..]->(:Rule {run: {run}, condition: "post"})-[*1]->(leaf:Goal {run: {run}, condition: "post"}) 84 | WHERE NOT ()-->(root) AND NOT (leaf)-->() 85 | WITH length(path) AS maxLen 86 | ORDER BY maxLen DESC 87 | LIMIT 1 88 | WITH maxLen 89 | 90 | MATCH path = (root:Goal {run: {run}, condition: "post"})-[*0..]->(rule:Rule {run: {run}, condition: "post"})-[*1]->(leaf:Goal {run: {run}, condition: "post"}) 91 | WHERE NOT ()-->(root) AND NOT (leaf)-->() AND length(path) = maxLen 92 | 93 | WITH DISTINCT rule 94 | MATCH (rule)-[*1]->(leaf:Goal {run: {run}, condition: "post"}) 95 | WITH rule, collect(leaf) AS leaves 96 | 97 | RETURN rule, leaves; 98 | `) 99 | if err != nil { 100 | return nil, nil, nil, err 101 | } 102 | 103 | leavesRaw, err := stmtLeaves.QueryNeo(map[string]interface{}{ 104 | "run": diffRunID, 105 | }) 106 | if err != nil { 107 | return nil, nil, nil, err 108 | } 109 | 110 | leavesAll, _, err := leavesRaw.All() 111 | if err != nil { 112 | return nil, nil, nil, err 113 | } 114 | 115 | missing := make([]*fi.Missing, len(leavesAll)) 116 | 117 | for j := range leavesAll { 118 | 119 | rule := leavesAll[j][0].(graph.Node) 120 | m := &fi.Missing{ 121 | Rule: &fi.Rule{ 122 | ID: rule.Properties["id"].(string), 123 | Label: rule.Properties["label"].(string), 124 | Table: rule.Properties["table"].(string), 125 | Type: rule.Properties["type"].(string), 126 | }, 127 | Goals: make([]*fi.Goal, 0, 2), 128 | } 129 | 130 | // Add all leaves. 131 | leaves := leavesAll[j][1].([]interface{}) 132 | for l := range leaves { 133 | 134 | leaf := leaves[l].(graph.Node) 135 | 136 | m.Goals = append(m.Goals, &fi.Goal{ 137 | ID: leaf.Properties["id"].(string), 138 | Label: leaf.Properties["label"].(string), 139 | Table: leaf.Properties["table"].(string), 140 | Time: leaf.Properties["time"].(string), 141 | CondHolds: leaf.Properties["condition_holds"].(bool), 142 | }) 143 | } 144 | 145 | missing[j] = m 146 | } 147 | 148 | err = leavesRaw.Close() 149 | if err != nil { 150 | return nil, nil, nil, err 151 | } 152 | 153 | err = stmtLeaves.Close() 154 | if err != nil { 155 | return nil, nil, nil, err 156 | } 157 | 158 | // Query for imported differential provenance. 159 | stmtProv, err := n.Conn1.PrepareNeo(` 160 | MATCH path = ({run: {run}, condition: "post"})-[:DUETO*1]->({run: {run}, condition: "post"}) 161 | RETURN path; 162 | `) 163 | if err != nil { 164 | return nil, nil, nil, err 165 | } 166 | 167 | edgesRaw, err := stmtProv.QueryNeo(map[string]interface{}{ 168 | "run": diffRunID, 169 | }) 170 | if err != nil { 171 | return nil, nil, nil, err 172 | } 173 | 174 | diffEdges := make([]graph.Path, 0, 10) 175 | 176 | for err == nil { 177 | 178 | var edgeRaw []interface{} 179 | 180 | edgeRaw, _, err = edgesRaw.NextNeo() 181 | if err != nil && err != io.EOF { 182 | return nil, nil, nil, err 183 | } else if err == nil { 184 | 185 | // Type-assert raw edge into well-defined struct. 186 | edge := edgeRaw[0].(graph.Path) 187 | 188 | // Append to slice of edges. 189 | diffEdges = append(diffEdges, edge) 190 | } 191 | } 192 | 193 | err = edgesRaw.Close() 194 | if err != nil { 195 | return nil, nil, nil, err 196 | } 197 | 198 | edgesRaw, err = stmtProv.QueryNeo(map[string]interface{}{ 199 | "run": failedRuns[i], 200 | }) 201 | if err != nil { 202 | return nil, nil, nil, err 203 | } 204 | 205 | failedEdges := make([]graph.Path, 0, 10) 206 | 207 | for err == nil { 208 | 209 | var edgeRaw []interface{} 210 | 211 | edgeRaw, _, err = edgesRaw.NextNeo() 212 | if err != nil && err != io.EOF { 213 | return nil, nil, nil, err 214 | } else if err == nil { 215 | 216 | // Type-assert raw edge into well-defined struct. 217 | edge := edgeRaw[0].(graph.Path) 218 | 219 | // Append to slice of edges. 220 | failedEdges = append(failedEdges, edge) 221 | } 222 | } 223 | 224 | // Pass to DOT string generator. 225 | diffDot, failedDot, err := createDiffDot(diffRunID, diffEdges, failedRuns[i], failedEdges, 0, successPostProv, missing) 226 | if err != nil { 227 | return nil, nil, nil, err 228 | } 229 | 230 | err = stmtProv.Close() 231 | if err != nil { 232 | return nil, nil, nil, err 233 | } 234 | 235 | diffDots[i] = diffDot 236 | failedDots[i] = failedDot 237 | missingEvents[i] = missing 238 | } 239 | 240 | fmt.Printf("done\n\n") 241 | 242 | return diffDots, failedDots, missingEvents, nil 243 | } 244 | -------------------------------------------------------------------------------- /graphing/extensions.go: -------------------------------------------------------------------------------- 1 | package graphing 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | graph "github.com/johnnadratowski/golang-neo4j-bolt-driver/structures/graph" 8 | ) 9 | 10 | // Functions. 11 | 12 | // GenerateExtensions 13 | func (n *Neo4J) GenerateExtensions() (bool, []string, error) { 14 | 15 | // Track if all runs achieve the antecedent. 16 | allAchievedPre := true 17 | 18 | // Prepare slice of extensions. 19 | extensions := make([]string, 0, 3) 20 | 21 | // Prepare map for adding extensions only once per rule. 22 | rulesState := make(map[string]string) 23 | 24 | // Query for antecedent achievement per run. 25 | preAchievedRows, err := n.Conn1.QueryNeo(` 26 | MATCH (pre:Goal {condition: "pre", table: "pre", condition_holds: true}) 27 | WHERE pre.run < 1000 28 | RETURN collect(pre) AS pres; 29 | `, nil) 30 | if err != nil { 31 | return false, nil, err 32 | } 33 | 34 | var preAchievedRaw []interface{} 35 | 36 | preAchievedRaw, _, err = preAchievedRows.NextNeo() 37 | if err != nil && err != io.EOF { 38 | return false, nil, err 39 | } else if err == nil { 40 | 41 | // Collect actual result we are interested in. 42 | preAchieved := preAchievedRaw[0].([]interface{}) 43 | 44 | // Only in case this slice has as many members as 45 | // our execution has runs, we do not switch the 46 | // allAchievedPre flag to false. 47 | if len(preAchieved) < len(n.Runs) { 48 | allAchievedPre = false 49 | } 50 | } 51 | 52 | err = preAchievedRows.Close() 53 | if err != nil { 54 | return false, nil, err 55 | } 56 | 57 | if !allAchievedPre { 58 | 59 | // In case not all runs achieved the antecedent, 60 | // we query the successful (first) run and collect 61 | // all network events. 62 | 63 | asyncEventsRows, err := n.Conn1.QueryNeo(` 64 | MATCH (r:Rule {run: 0, condition: "pre", type: "async"}) 65 | WHERE (:Goal {run: 0, condition: "pre", condition_holds: true})-[*1]->(r)-[*1]->(:Goal {run: 0, condition: "pre", condition_holds: false})-[*1]->(:Rule {run: 0, condition: "pre"}) OR (:Goal {run: 0, condition: "pre", condition_holds: false})-[*1]->(r) 66 | RETURN r; 67 | `, nil) 68 | if err != nil { 69 | return false, nil, err 70 | } 71 | 72 | asyncEventsRaw, _, err := asyncEventsRows.All() 73 | if err != nil { 74 | return false, nil, err 75 | } 76 | 77 | for i := range asyncEventsRaw { 78 | 79 | rule := asyncEventsRaw[i][0].(graph.Node) 80 | 81 | // Add rule to extension suggestions only 82 | // in case we did not already do so. 83 | rulesState[rule.Properties["table"].(string)] = fmt.Sprintf("%s(node, ...)@async :- ...;", rule.Properties["table"].(string)) 84 | } 85 | 86 | for rule := range rulesState { 87 | 88 | // Append an extension suggestion to the final slice. 89 | extensions = append(extensions, rulesState[rule]) 90 | } 91 | 92 | err = asyncEventsRows.Close() 93 | if err != nil { 94 | return false, nil, err 95 | } 96 | } 97 | 98 | return allAchievedPre, extensions, nil 99 | } 100 | -------------------------------------------------------------------------------- /graphing/hazard-analysis.go: -------------------------------------------------------------------------------- 1 | package graphing 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "io/ioutil" 8 | "path/filepath" 9 | 10 | "github.com/awalterschulze/gographviz" 11 | ) 12 | 13 | // Functions. 14 | 15 | // CreateHazardAnalysis 16 | func (n *Neo4J) CreateHazardAnalysis(faultInjOut string) ([]*gographviz.Graph, error) { 17 | 18 | fmt.Printf("Running hazard window analysis... ") 19 | 20 | dots := make([]*gographviz.Graph, len(n.Runs)) 21 | 22 | for i := range n.Runs { 23 | 24 | // Space-time file name in fault injector directory. 25 | fiSpaceTime := filepath.Join(faultInjOut, fmt.Sprintf("run_%d_spacetime.dot", n.Runs[i].Iteration)) 26 | 27 | // Load current space-time diagram. 28 | spaceTimeDotBytes, err := ioutil.ReadFile(fiSpaceTime) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | // Read DOT data. 34 | spaceTimeGraph, err := gographviz.Read(spaceTimeDotBytes) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | for j := range spaceTimeGraph.Nodes.Nodes { 40 | 41 | spaceTimeGraph.Nodes.Nodes[j].Attrs.Extend(map[gographviz.Attr]string{ 42 | "style": "\"solid, filled\"", 43 | "color": "\"lightgrey\"", 44 | "fillcolor": "\"lightgrey\"", 45 | }) 46 | 47 | // Split into naming and time parts. 48 | nameParts := strings.Split(spaceTimeGraph.Nodes.Nodes[j].Name, "_") 49 | 50 | // Possibly selecting the time of the node here. 51 | // If this is not actually the time, it does not 52 | // pose a problem as our map below only works on 53 | // actual timesteps. 54 | nodeTime := nameParts[(len(nameParts) - 1)] 55 | 56 | // TODO: If Pre() is not specified over only 57 | // one global column, but rather over 58 | // node-individual local state, we have 59 | // to proceed differently here! 60 | _, preHolds := n.Runs[i].TimePreHolds[nodeTime] 61 | if preHolds { 62 | 63 | spaceTimeGraph.Nodes.Nodes[j].Attrs.Extend(map[gographviz.Attr]string{ 64 | "color": "\"firebrick\"", 65 | "fillcolor": "\"firebrick\"", 66 | }) 67 | } 68 | 69 | // TODO: If Post() is not specified over only 70 | // one global column, but rather over 71 | // node-individual local state, we have 72 | // to proceed differently here! 73 | _, postHolds := n.Runs[i].TimePostHolds[nodeTime] 74 | if postHolds { 75 | 76 | spaceTimeGraph.Nodes.Nodes[j].Attrs.Extend(map[gographviz.Attr]string{ 77 | "fillcolor": "\"deepskyblue\"", 78 | }) 79 | } 80 | } 81 | 82 | dots[i] = spaceTimeGraph 83 | } 84 | 85 | fmt.Printf("done\n\n") 86 | 87 | return dots, nil 88 | } 89 | -------------------------------------------------------------------------------- /graphing/helpers.go: -------------------------------------------------------------------------------- 1 | package graphing 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "os/exec" 9 | 10 | neo4j "github.com/johnnadratowski/golang-neo4j-bolt-driver" 11 | fi "github.com/numbleroot/nemo/faultinjectors" 12 | ) 13 | 14 | // Functions. 15 | 16 | // InitGraphDB 17 | func (n *Neo4J) InitGraphDB(boltURI string, runs []*fi.Run) error { 18 | 19 | // Run the docker start command. 20 | fmt.Printf("Starting docker containers... ") 21 | cmd := exec.Command("sudo", "docker-compose", "-f", "docker-compose.yml", "up", "-d") 22 | out, err := cmd.CombinedOutput() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | if !strings.Contains(string(out), "done") { 28 | return fmt.Errorf("Wrong return value from docker-compose up command: %s", out) 29 | } 30 | fmt.Printf("done\n") 31 | 32 | // Wait long enough for graph database to be up. 33 | time.Sleep(10 * time.Second) 34 | 35 | driver := neo4j.NewDriver() 36 | 37 | // Connect to bolt endpoint. 38 | c1, err := driver.OpenNeo(boltURI) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | c2, err := driver.OpenNeo(boltURI) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | n.Conn1 = c1 49 | n.Conn2 = c2 50 | n.Runs = runs 51 | 52 | fmt.Println() 53 | 54 | return nil 55 | } 56 | 57 | // CloseDB properly shuts down the Neo4J connection. 58 | func (n *Neo4J) CloseDB() error { 59 | 60 | err := n.Conn1.Close() 61 | if err != nil { 62 | return err 63 | } 64 | 65 | err = n.Conn2.Close() 66 | if err != nil { 67 | return err 68 | } 69 | 70 | time.Sleep(2 * time.Second) 71 | 72 | // Shut down docker container. 73 | fmt.Printf("Shutting down docker containers... ") 74 | cmd := exec.Command("sudo", "docker-compose", "-f", "docker-compose.yml", "down") 75 | out, err := cmd.CombinedOutput() 76 | if err != nil { 77 | return err 78 | } 79 | 80 | if !strings.Contains(string(out), "done") { 81 | return fmt.Errorf("Wrong return value from docker-compose down command: %s", out) 82 | } 83 | fmt.Printf("done\n") 84 | 85 | return nil 86 | } 87 | -------------------------------------------------------------------------------- /graphing/pre-post-prov.go: -------------------------------------------------------------------------------- 1 | package graphing 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/awalterschulze/gographviz" 8 | neo4j "github.com/johnnadratowski/golang-neo4j-bolt-driver" 9 | graph "github.com/johnnadratowski/golang-neo4j-bolt-driver/structures/graph" 10 | fi "github.com/numbleroot/nemo/faultinjectors" 11 | ) 12 | 13 | // Structs. 14 | 15 | // Neo4J 16 | type Neo4J struct { 17 | Conn1 neo4j.Conn 18 | Conn2 neo4j.Conn 19 | Runs []*fi.Run 20 | } 21 | 22 | // Functions. 23 | 24 | // loadProv 25 | func (n *Neo4J) loadProv(iteration uint, provCond string, provData *fi.ProvData) error { 26 | 27 | stmtGoal, err := n.Conn1.PrepareNeo(` 28 | CREATE (goal:Goal {id: {id}, run: {run}, condition: {condition}, label: {label}, table: {table}, time: {time}, condition_holds: {condition_holds}}); 29 | `) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | var resCnt int64 = 0 35 | 36 | for j := range provData.Goals { 37 | 38 | // Create a goal node. 39 | res, err := stmtGoal.ExecNeo(map[string]interface{}{ 40 | "id": provData.Goals[j].ID, 41 | "run": iteration, 42 | "condition": provCond, 43 | "label": provData.Goals[j].Label, 44 | "table": provData.Goals[j].Table, 45 | "time": provData.Goals[j].Time, 46 | "condition_holds": provData.Goals[j].CondHolds, 47 | }) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | // Collect affected rows information. 53 | rowsAff, err := res.RowsAffected() 54 | if err != nil { 55 | return err 56 | } 57 | resCnt += rowsAff 58 | } 59 | 60 | err = stmtGoal.Close() 61 | if err != nil { 62 | return err 63 | } 64 | 65 | // During first run: create constraints and indexes. 66 | if iteration == 0 { 67 | 68 | _, err = n.Conn1.ExecNeo(` 69 | CREATE CONSTRAINT ON (goal:Goal) ASSERT goal.id IS UNIQUE; 70 | `, nil) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | _, err = n.Conn1.ExecNeo(` 76 | CREATE INDEX ON :Goal(run); 77 | `, nil) 78 | if err != nil { 79 | return err 80 | } 81 | } 82 | 83 | // Verify number of inserted elements. 84 | if int64(len(provData.Goals)) != resCnt { 85 | return fmt.Errorf("Run %d: inserted number of goals (%d) does not equal number of antecedent provenance goals (%d)", iteration, resCnt, len(provData.Goals)) 86 | } 87 | 88 | resCnt = 0 89 | 90 | stmtRule, err := n.Conn1.PrepareNeo(` 91 | CREATE (n:Rule {id: {id}, run: {run}, condition: {condition}, label: {label}, table: {table}, type: {type}}); 92 | `) 93 | if err != nil { 94 | return err 95 | } 96 | 97 | for j := range provData.Rules { 98 | 99 | // Create a rule node. 100 | res, err := stmtRule.ExecNeo(map[string]interface{}{ 101 | "id": provData.Rules[j].ID, 102 | "run": iteration, 103 | "condition": provCond, 104 | "label": provData.Rules[j].Label, 105 | "table": provData.Rules[j].Table, 106 | "type": provData.Rules[j].Type, 107 | }) 108 | if err != nil { 109 | return err 110 | } 111 | 112 | // Collect affected rows information. 113 | rowsAff, err := res.RowsAffected() 114 | if err != nil { 115 | return err 116 | } 117 | resCnt += rowsAff 118 | } 119 | 120 | err = stmtRule.Close() 121 | if err != nil { 122 | return err 123 | } 124 | 125 | // During first run: create constraints and indexes. 126 | if iteration == 0 { 127 | 128 | _, err = n.Conn1.ExecNeo(` 129 | CREATE CONSTRAINT ON (rule:Rule) ASSERT rule.id IS UNIQUE; 130 | `, nil) 131 | if err != nil { 132 | return err 133 | } 134 | 135 | _, err = n.Conn1.ExecNeo(` 136 | CREATE INDEX ON :Rule(run); 137 | `, nil) 138 | if err != nil { 139 | return err 140 | } 141 | } 142 | 143 | // Verify number of inserted elements. 144 | if int64(len(provData.Rules)) != resCnt { 145 | return fmt.Errorf("Run %d: inserted number of rules (%d) does not equal number of antecedent provenance rules (%d)", iteration, resCnt, len(provData.Rules)) 146 | } 147 | 148 | resCnt = 0 149 | 150 | stmtGoalRuleEdge, err := n.Conn1.PrepareNeo(` 151 | MATCH (goal:Goal {id: {from}, run: {run}, condition: {condition}}) 152 | MATCH (rule:Rule {id: {to}, run: {run}, condition: {condition}}) 153 | MERGE (goal)-[:DUETO]->(rule); 154 | `) 155 | if err != nil { 156 | return err 157 | } 158 | 159 | stmtRuleGoalEdge, err := n.Conn2.PrepareNeo(` 160 | MATCH (rule:Rule {id: {from}, run: {run}, condition: {condition}}) 161 | MATCH (goal:Goal {id: {to}, run: {run}, condition: {condition}}) 162 | MERGE (rule)-[:DUETO]->(goal); 163 | `) 164 | if err != nil { 165 | return err 166 | } 167 | 168 | for j := range provData.Edges { 169 | 170 | var res neo4j.Result 171 | 172 | // Create an edge relation. 173 | if strings.Contains(provData.Edges[j].From, "goal") { 174 | res, err = stmtGoalRuleEdge.ExecNeo(map[string]interface{}{ 175 | "from": provData.Edges[j].From, 176 | "to": provData.Edges[j].To, 177 | "run": iteration, 178 | "condition": provCond, 179 | }) 180 | } else { 181 | res, err = stmtRuleGoalEdge.ExecNeo(map[string]interface{}{ 182 | "from": provData.Edges[j].From, 183 | "to": provData.Edges[j].To, 184 | "run": iteration, 185 | "condition": provCond, 186 | }) 187 | } 188 | if err != nil { 189 | return err 190 | } 191 | 192 | // Track number of created relationships. 193 | stats := res.Metadata()["stats"].(map[string]interface{}) 194 | resCnt += stats["relationships-created"].(int64) 195 | } 196 | 197 | err = stmtGoalRuleEdge.Close() 198 | if err != nil { 199 | return err 200 | } 201 | 202 | err = stmtRuleGoalEdge.Close() 203 | if err != nil { 204 | return err 205 | } 206 | 207 | // Verify number of inserted elements. 208 | if int64(len(provData.Edges)) != resCnt { 209 | return fmt.Errorf("Run %d: inserted number of edges (%d) does not equal number of antecedent provenance edges (%d)", iteration, resCnt, len(provData.Edges)) 210 | } 211 | 212 | return nil 213 | } 214 | 215 | // markConditionHolds walks the provenance graph of 216 | // specified run and condition and marks goals depending 217 | // on whether the specified condition holds. 218 | func (n *Neo4J) markConditionHolds(iteration uint, provCond string) error { 219 | 220 | stmtMarkCond, err := n.Conn1.PrepareNeo(` 221 | MATCH (g:Goal {run: {run}, condition: {condition}})-[*1]->(r:Rule {run: {run}, condition: {condition}}) 222 | WHERE (:Goal {run: {run}, condition: {condition}, table: {condition}})-[*1]->(:Rule {run: {run}, condition: {condition}, table: {condition}})-[*1]->(g) AND NOT ()-->(:Goal {run: {run}, condition: {condition}, table: {condition}})-[*1]->(:Rule {run: {run}, condition: {condition}, table: {condition}})-[*1]->(g) 223 | WITH g.table AS rule 224 | 225 | MATCH (n:Goal {run: {run}, condition: {condition}}) 226 | WHERE n.table = {condition} OR n.table = rule 227 | SET n.condition_holds = true 228 | `) 229 | 230 | _, err = stmtMarkCond.ExecNeo(map[string]interface{}{ 231 | "run": iteration, 232 | "condition": provCond, 233 | }) 234 | if err != nil { 235 | return err 236 | } 237 | 238 | err = stmtMarkCond.Close() 239 | if err != nil { 240 | return err 241 | } 242 | 243 | return nil 244 | } 245 | 246 | // LoadRawProvenance 247 | func (n *Neo4J) LoadRawProvenance() error { 248 | 249 | fmt.Printf("Loading raw provenance data...\n") 250 | 251 | for i := range n.Runs { 252 | 253 | // Load antecedent provenance. 254 | fmt.Printf("\t[%d] Antecedent provenance... ", n.Runs[i].Iteration) 255 | err := n.loadProv(n.Runs[i].Iteration, "pre", n.Runs[i].PreProv) 256 | if err != nil { 257 | return err 258 | } 259 | fmt.Printf("done\n") 260 | 261 | // Taint goals for which the antecedent holds. 262 | err = n.markConditionHolds(n.Runs[i].Iteration, "pre") 263 | if err != nil { 264 | return err 265 | } 266 | 267 | // Load consequent provenance. 268 | fmt.Printf("\t[%d] Consequent provenance... ", n.Runs[i].Iteration) 269 | err = n.loadProv(n.Runs[i].Iteration, "post", n.Runs[i].PostProv) 270 | if err != nil { 271 | return err 272 | } 273 | fmt.Printf("done\n") 274 | 275 | // Taint goals for which the consequent holds. 276 | err = n.markConditionHolds(n.Runs[i].Iteration, "post") 277 | if err != nil { 278 | return err 279 | } 280 | } 281 | 282 | fmt.Println() 283 | 284 | return nil 285 | } 286 | 287 | // PullPrePostProv 288 | func (n *Neo4J) PullPrePostProv() ([]*gographviz.Graph, []*gographviz.Graph, []*gographviz.Graph, []*gographviz.Graph, error) { 289 | 290 | fmt.Printf("Pulling antecedent and consequent provenance... ") 291 | 292 | preDots := make([]*gographviz.Graph, len(n.Runs)) 293 | postDots := make([]*gographviz.Graph, len(n.Runs)) 294 | preCleanDots := make([]*gographviz.Graph, len(n.Runs)) 295 | postCleanDots := make([]*gographviz.Graph, len(n.Runs)) 296 | 297 | // Query for imported correctness condition provenance. 298 | stmtProv, err := n.Conn1.PrepareNeo(` 299 | MATCH path = ({run: {run}, condition: {condition}})-[:DUETO*1]->({run: {run}, condition: {condition}}) 300 | RETURN path; 301 | `) 302 | if err != nil { 303 | return nil, nil, nil, nil, err 304 | } 305 | 306 | for i := range n.Runs { 307 | 308 | preEdges := make([]graph.Path, 0, 20) 309 | postEdges := make([]graph.Path, 0, 20) 310 | preCleanEdges := make([]graph.Path, 0, 20) 311 | postCleanEdges := make([]graph.Path, 0, 20) 312 | 313 | preEdgesRaw, err := stmtProv.QueryNeo(map[string]interface{}{ 314 | "run": n.Runs[i].Iteration, 315 | "condition": "pre", 316 | }) 317 | if err != nil { 318 | return nil, nil, nil, nil, err 319 | } 320 | 321 | preEdgesRows, _, err := preEdgesRaw.All() 322 | if err != nil { 323 | return nil, nil, nil, nil, err 324 | } 325 | 326 | for p := range preEdgesRows { 327 | 328 | // Type-assert raw edge into well-defined struct. 329 | edge := preEdgesRows[p][0].(graph.Path) 330 | 331 | // Append to slice of edges. 332 | preEdges = append(preEdges, edge) 333 | } 334 | 335 | // Pass to DOT string generator. 336 | preDot, err := createDOT(preEdges, "pre") 337 | if err != nil { 338 | return nil, nil, nil, nil, err 339 | } 340 | 341 | err = preEdgesRaw.Close() 342 | if err != nil { 343 | return nil, nil, nil, nil, err 344 | } 345 | 346 | postEdgesRaw, err := stmtProv.QueryNeo(map[string]interface{}{ 347 | "run": n.Runs[i].Iteration, 348 | "condition": "post", 349 | }) 350 | if err != nil { 351 | return nil, nil, nil, nil, err 352 | } 353 | 354 | postEdgesRows, _, err := postEdgesRaw.All() 355 | if err != nil { 356 | return nil, nil, nil, nil, err 357 | } 358 | 359 | for p := range postEdgesRows { 360 | 361 | // Type-assert raw edge into well-defined struct. 362 | edge := postEdgesRows[p][0].(graph.Path) 363 | 364 | // Append to slice of edges. 365 | postEdges = append(postEdges, edge) 366 | } 367 | 368 | // Pass to DOT string generator. 369 | postDot, err := createDOT(postEdges, "post") 370 | if err != nil { 371 | return nil, nil, nil, nil, err 372 | } 373 | 374 | err = postEdgesRaw.Close() 375 | if err != nil { 376 | return nil, nil, nil, nil, err 377 | } 378 | 379 | preCleanEdgesRaw, err := stmtProv.QueryNeo(map[string]interface{}{ 380 | "run": (1000 + n.Runs[i].Iteration), 381 | "condition": "pre", 382 | }) 383 | if err != nil { 384 | return nil, nil, nil, nil, err 385 | } 386 | 387 | preCleanEdgesRows, _, err := preCleanEdgesRaw.All() 388 | if err != nil { 389 | return nil, nil, nil, nil, err 390 | } 391 | 392 | for p := range preCleanEdgesRows { 393 | 394 | // Type-assert raw edge into well-defined struct. 395 | edge := preCleanEdgesRows[p][0].(graph.Path) 396 | 397 | // Append to slice of edges. 398 | preCleanEdges = append(preCleanEdges, edge) 399 | } 400 | 401 | // Pass to DOT string generator. 402 | preCleanDot, err := createDOT(preCleanEdges, "pre") 403 | if err != nil { 404 | return nil, nil, nil, nil, err 405 | } 406 | 407 | err = preCleanEdgesRaw.Close() 408 | if err != nil { 409 | return nil, nil, nil, nil, err 410 | } 411 | 412 | postCleanEdgesRaw, err := stmtProv.QueryNeo(map[string]interface{}{ 413 | "run": (1000 + n.Runs[i].Iteration), 414 | "condition": "post", 415 | }) 416 | if err != nil { 417 | return nil, nil, nil, nil, err 418 | } 419 | 420 | postCleanEdgesRows, _, err := postCleanEdgesRaw.All() 421 | if err != nil { 422 | return nil, nil, nil, nil, err 423 | } 424 | 425 | for p := range postCleanEdgesRows { 426 | 427 | // Type-assert raw edge into well-defined struct. 428 | edge := postCleanEdgesRows[p][0].(graph.Path) 429 | 430 | // Append to slice of edges. 431 | postCleanEdges = append(postCleanEdges, edge) 432 | } 433 | 434 | // Pass to DOT string generator. 435 | postCleanDot, err := createDOT(postCleanEdges, "post") 436 | if err != nil { 437 | return nil, nil, nil, nil, err 438 | } 439 | 440 | err = postCleanEdgesRaw.Close() 441 | if err != nil { 442 | return nil, nil, nil, nil, err 443 | } 444 | 445 | preDots[i] = preDot 446 | postDots[i] = postDot 447 | preCleanDots[i] = preCleanDot 448 | postCleanDots[i] = postCleanDot 449 | } 450 | 451 | err = stmtProv.Close() 452 | if err != nil { 453 | return nil, nil, nil, nil, err 454 | } 455 | 456 | fmt.Printf("done\n\n") 457 | 458 | return preDots, postDots, preCleanDots, postCleanDots, nil 459 | } 460 | -------------------------------------------------------------------------------- /graphing/preprocessing.go: -------------------------------------------------------------------------------- 1 | package graphing 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "os/exec" 8 | 9 | graph "github.com/johnnadratowski/golang-neo4j-bolt-driver/structures/graph" 10 | ) 11 | 12 | // cleanCopyProv 13 | func (n *Neo4J) cleanCopyProv(iter uint, condition string) error { 14 | 15 | newID := 1000 + iter 16 | 17 | exportQuery := `CALL apoc.export.cypher.query(" 18 | MATCH path = (g1:Goal {run: ###RUN###, condition: '###CONDITION###'})-[*0..]->(g2:Goal {run: ###RUN###, condition: '###CONDITION###'}) 19 | RETURN path; 20 | ", "/tmp/clean-prov", {format: "cypher-shell", cypherFormat: "create"}) 21 | YIELD time 22 | RETURN time; 23 | ` 24 | 25 | exportQuery = strings.Replace(exportQuery, "###RUN###", fmt.Sprintf("%d", iter), -1) 26 | exportQuery = strings.Replace(exportQuery, "###CONDITION###", condition, -1) 27 | _, err := n.Conn1.ExecNeo(exportQuery, nil) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | // Replace run ID part of node ID in saved queries. 33 | sedIDLong := fmt.Sprintf("s/`id`:\"run_%d/`id`:\"run_%d/g", iter, newID) 34 | cmd := exec.Command("sudo", "docker", "exec", "graphdb", "sed", "-i", sedIDLong, "/tmp/clean-prov") 35 | out, err := cmd.CombinedOutput() 36 | if err != nil { 37 | return err 38 | } 39 | 40 | if strings.TrimSpace(string(out)) != "" { 41 | return fmt.Errorf("Wrong return value from docker-compose exec sed diffprov run ID command: %s", out) 42 | } 43 | 44 | // Replace run ID in saved queries. 45 | sedIDShort := fmt.Sprintf("s/`run`:%d/`run`:%d/g", iter, newID) 46 | cmd = exec.Command("sudo", "docker", "exec", "graphdb", "sed", "-i", sedIDShort, "/tmp/clean-prov") 47 | out, err = cmd.CombinedOutput() 48 | if err != nil { 49 | return err 50 | } 51 | 52 | if strings.TrimSpace(string(out)) != "" { 53 | return fmt.Errorf("Wrong return value from docker-compose exec sed diffprov run ID command: %s", out) 54 | } 55 | 56 | // Import modified difference graph as new one. 57 | _, err = n.Conn1.ExecNeo(`CALL apoc.cypher.runFile("/tmp/clean-prov", {statistics: false});`, nil) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | return nil 63 | } 64 | 65 | // collapseNextChains 66 | func (n *Neo4J) collapseNextChains(iter uint, condition string) error { 67 | 68 | run := (1000 + iter) 69 | 70 | stmtCollapseNext, err := n.Conn2.PrepareNeo(` 71 | MATCH path = (r1:Rule {run: {run}, condition: {condition}, type: "next"})-[*1..]->(g:Goal {run: {run}, condition: {condition}})-[*1..]->(r2:Rule {run: {run}, condition: {condition}, type: "next"}) 72 | WHERE all(node IN nodes(path) WHERE node.type = "next" OR not(exists(node.type))) 73 | WITH path, nodes(path) AS nodesRaw, length(path) AS len 74 | UNWIND nodesRaw AS node 75 | WITH path, collect(ID(node)) AS nodes, len 76 | RETURN path, nodes 77 | ORDER BY len DESC; 78 | `) 79 | if err != nil { 80 | return err 81 | } 82 | 83 | nextPaths, err := stmtCollapseNext.QueryNeo(map[string]interface{}{ 84 | "run": run, 85 | "condition": condition, 86 | }) 87 | if err != nil { 88 | return err 89 | } 90 | 91 | nextPathsAll, _, err := nextPaths.All() 92 | if err != nil { 93 | return err 94 | } 95 | 96 | err = nextPaths.Close() 97 | if err != nil { 98 | return err 99 | } 100 | 101 | // Create structure to track top-level @next chains per iteration. 102 | nextChains := make([][]graph.Node, 0, len(nextPathsAll)) 103 | nextChainIDs := make([][]int64, 0, len(nextPathsAll)) 104 | 105 | // Create map to quickly check node containment in path. 106 | nextChainsNodes := make(map[int64]bool) 107 | 108 | for j := range nextPathsAll { 109 | 110 | newChain := false 111 | paths := nextPathsAll[j][0].(graph.Path) 112 | 113 | nodesRaw := nextPathsAll[j][1].([]interface{}) 114 | nodes := make([]int64, len(nodesRaw)) 115 | 116 | for n := range nodes { 117 | 118 | nodes[n] = nodesRaw[n].(int64) 119 | 120 | _, found := nextChainsNodes[nodes[n]] 121 | if !found { 122 | newChain = true 123 | } 124 | } 125 | 126 | if newChain { 127 | 128 | // Add these next chain paths to global structure. 129 | nextChains = append(nextChains, paths.Nodes) 130 | nextChainIDs = append(nextChainIDs, nodes) 131 | 132 | // Also add contained node labels to map so that 133 | // we can decide on future paths. 134 | for n := range nodes { 135 | nextChainsNodes[nodes[n]] = true 136 | } 137 | } 138 | } 139 | 140 | err = stmtCollapseNext.Close() 141 | if err != nil { 142 | return err 143 | } 144 | 145 | // Find predecessor relations to chain. 146 | stmtPred, err := n.Conn1.PrepareNeo(` 147 | MATCH (pred:Goal {run: {run}, condition: {condition}})-[*1]->(root:Rule {run: {run}, condition: {condition}}) 148 | WHERE ID(root) = {rootID} 149 | WITH collect(ID(pred)) AS preds 150 | RETURN preds; 151 | `) 152 | if err != nil { 153 | return err 154 | } 155 | 156 | preds := make([][]int64, len(nextChains)) 157 | 158 | for i := range nextChains { 159 | 160 | predsRaw, err := stmtPred.QueryNeo(map[string]interface{}{ 161 | "run": run, 162 | "condition": condition, 163 | "rootID": nextChainIDs[i][0], 164 | }) 165 | if err != nil { 166 | return err 167 | } 168 | 169 | predsAll, _, err := predsRaw.All() 170 | if err != nil { 171 | return err 172 | } 173 | 174 | err = predsRaw.Close() 175 | if err != nil { 176 | return err 177 | } 178 | 179 | preds[i] = make([]int64, 0, 1) 180 | 181 | for p := range predsAll { 182 | 183 | // Extract all predecessor nodes and append them 184 | // individually to the global tracking structure. 185 | predsParsed := predsAll[p][0].([]interface{}) 186 | for r := range predsParsed { 187 | preds[i] = append(preds[i], predsParsed[r].(int64)) 188 | } 189 | } 190 | } 191 | 192 | err = stmtPred.Close() 193 | if err != nil { 194 | return err 195 | } 196 | 197 | // Find all "outwards" relations of chain. 198 | stmtSucc, err := n.Conn2.PrepareNeo(` 199 | MATCH (leaf:Rule {run: {run}, condition: {condition}})-[*1]->(succ:Goal {run: {run}, condition: {condition}}) 200 | WHERE ID(leaf) = {leafID} 201 | WITH collect(ID(succ)) AS succs 202 | RETURN succs; 203 | `) 204 | if err != nil { 205 | return err 206 | } 207 | 208 | succs := make([][]int64, len(nextChains)) 209 | 210 | for i := range nextChains { 211 | 212 | succsRaw, err := stmtSucc.QueryNeo(map[string]interface{}{ 213 | "run": run, 214 | "condition": condition, 215 | "leafID": nextChainIDs[i][(len(nextChainIDs[i]) - 1)], 216 | }) 217 | if err != nil { 218 | return err 219 | } 220 | 221 | succsAll, _, err := succsRaw.All() 222 | if err != nil { 223 | return err 224 | } 225 | 226 | err = succsRaw.Close() 227 | if err != nil { 228 | return err 229 | } 230 | 231 | succs[i] = make([]int64, 0, 1) 232 | 233 | for p := range succsAll { 234 | 235 | // Extract all successor nodes and append them 236 | // individually to the global tracking structure. 237 | succsParsed := succsAll[p][0].([]interface{}) 238 | for r := range succsParsed { 239 | succs[i] = append(succs[i], succsParsed[r].(int64)) 240 | } 241 | } 242 | } 243 | 244 | err = stmtSucc.Close() 245 | if err != nil { 246 | return err 247 | } 248 | 249 | for i := range nextChains { 250 | 251 | label := fmt.Sprintf("%s_collapsed", nextChains[i][0].Properties["table"]) 252 | id := fmt.Sprintf("run_%d_%s_%s_%d", run, condition, label, i) 253 | 254 | var predsIDs string 255 | for j := range preds[i] { 256 | 257 | if predsIDs == "" { 258 | predsIDs = fmt.Sprintf("[%d", preds[i][j]) 259 | } else { 260 | predsIDs = fmt.Sprintf("%s, %d", predsIDs, preds[i][j]) 261 | } 262 | } 263 | predsIDs = fmt.Sprintf("%s]", predsIDs) 264 | 265 | var succsIDs string 266 | for j := range succs[i] { 267 | 268 | if succsIDs == "" { 269 | succsIDs = fmt.Sprintf("[%d", succs[i][j]) 270 | } else { 271 | succsIDs = fmt.Sprintf("%s, %d", succsIDs, succs[i][j]) 272 | } 273 | } 274 | succsIDs = fmt.Sprintf("%s]", succsIDs) 275 | 276 | // Create new nodes representing the intent of the 277 | // captured @next chains. 278 | _, err := n.Conn1.ExecNeo(` 279 | CREATE (repl:Rule {run: {run}, condition: {condition}, id: {id}, label: {label}, table: {table}, type: "collapsed"}); 280 | `, map[string]interface{}{ 281 | "run": run, 282 | "condition": condition, 283 | "id": id, 284 | "label": label, 285 | "table": nextChains[i][0].Properties["table"], 286 | }) 287 | if err != nil { 288 | return err 289 | } 290 | 291 | // Connect newly created collapsed next node with 292 | // predecessors and successors. 293 | addPredsSuccsQuery := ` 294 | MATCH (pred:Goal {run: ###RUN###, condition: "###CONDITION###"}), (coll:Rule {run: ###RUN###, condition: "###CONDITION###", id: "###ID###", type: "collapsed"}), (succ:Goal {run: ###RUN###, condition: "###CONDITION###"}) 295 | WHERE ID(pred) IN ###PRED_IDs### AND ID(succ) IN ###SUCC_IDs### 296 | MERGE (pred)-[:DUETO]->(coll) 297 | MERGE (coll)-[:DUETO]->(succ); 298 | ` 299 | addPredsSuccsQuery = strings.Replace(addPredsSuccsQuery, "###RUN###", fmt.Sprintf("%d", run), -1) 300 | addPredsSuccsQuery = strings.Replace(addPredsSuccsQuery, "###CONDITION###", condition, -1) 301 | addPredsSuccsQuery = strings.Replace(addPredsSuccsQuery, "###ID###", id, -1) 302 | addPredsSuccsQuery = strings.Replace(addPredsSuccsQuery, "###PRED_IDs###", predsIDs, -1) 303 | addPredsSuccsQuery = strings.Replace(addPredsSuccsQuery, "###SUCC_IDs###", succsIDs, -1) 304 | 305 | _, err = n.Conn2.ExecNeo(addPredsSuccsQuery, nil) 306 | if err != nil { 307 | return err 308 | } 309 | } 310 | 311 | // Delete extracted next chain. 312 | stmtDelChainRaw := ` 313 | MATCH path = (r:Rule {run: {run}, condition: {condition}, type: "next"})-[*1..]->(g:Goal {run: {run}, condition: {condition}})-[*1..]->(l:Rule {run: {run}, condition: {condition}, type: "next"}) 314 | WHERE all(node IN nodes(path) WHERE ID(node) IN ###CHAIN_IDs###) 315 | WITH path, nodes(path) AS nodes, length(path) AS len 316 | ORDER BY len DESC 317 | UNWIND nodes AS node 318 | DETACH DELETE node; 319 | ` 320 | 321 | // Create string containing all IDs to delete in Cypher format. 322 | deleteIDs := make([]string, 0, len(nextChainsNodes)) 323 | for id := range nextChainsNodes { 324 | deleteIDs = append(deleteIDs, fmt.Sprintf("%d", id)) 325 | } 326 | deleteIDsString := strings.Join(deleteIDs, ", ") 327 | stmtDelChainRaw = strings.Replace(stmtDelChainRaw, "###CHAIN_IDs###", fmt.Sprintf("[%s]", deleteIDsString), -1) 328 | 329 | stmtDelChain, err := n.Conn1.PrepareNeo(stmtDelChainRaw) 330 | if err != nil { 331 | return err 332 | } 333 | 334 | _, err = stmtDelChain.ExecNeo(map[string]interface{}{ 335 | "run": run, 336 | "condition": condition, 337 | }) 338 | if err != nil { 339 | return err 340 | } 341 | 342 | err = stmtDelChain.Close() 343 | if err != nil { 344 | return err 345 | } 346 | 347 | return nil 348 | } 349 | 350 | // SimplifyProv 351 | func (n *Neo4J) SimplifyProv(iters []uint) error { 352 | 353 | fmt.Printf("Preprocessing provenance graphs... ") 354 | 355 | for i := range iters { 356 | 357 | // Clean-copy antecedent provenance (run: 1000+). 358 | err := n.cleanCopyProv(iters[i], "pre") 359 | if err != nil { 360 | return err 361 | } 362 | 363 | // Clean-copy consequent provenance (run: 1000+). 364 | err = n.cleanCopyProv(iters[i], "post") 365 | if err != nil { 366 | return err 367 | } 368 | 369 | // Do preprocessing over graphs of run 1000+: 370 | 371 | // Collapse @next chains in antecedent provenance. 372 | err = n.collapseNextChains(iters[i], "pre") 373 | if err != nil { 374 | return err 375 | } 376 | 377 | // Collapse @next chains in consequent provenance. 378 | err = n.collapseNextChains(iters[i], "post") 379 | if err != nil { 380 | return err 381 | } 382 | } 383 | 384 | fmt.Printf("done\n\n") 385 | 386 | return nil 387 | } 388 | -------------------------------------------------------------------------------- /graphing/prototype.go: -------------------------------------------------------------------------------- 1 | package graphing 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // extractProtos extracts the intersection-prototype 8 | // and union-prototype from all iterations. 9 | func (n *Neo4J) extractProtos(iters []uint, condition string) ([]string, []string, error) { 10 | 11 | stmtCondRules, err := n.Conn1.PrepareNeo(` 12 | MATCH path = (root:Goal {run: {run}, condition: {condition}})-[*1]->(r1:Rule {run: {run}, condition: {condition}})-[*1..]->(r2:Rule {run: {run}, condition: {condition}}) 13 | OPTIONAL MATCH (g:Goal {run: {run}, condition: "pre", condition_holds: true}) 14 | WITH path, root, collect(g) AS existsSuccess, length(path) AS len 15 | WHERE size(existsSuccess) > 0 AND not(()-->(root)) 16 | WITH path, len 17 | ORDER BY len DESC 18 | WITH collect(nodes(path)) AS nodes 19 | WITH reduce(output = [], node IN nodes | output + node) AS nodes 20 | WITH filter(node IN nodes WHERE exists(node.type)) AS rules 21 | UNWIND rules AS rule 22 | WITH collect(DISTINCT rule.table) AS rules 23 | RETURN rules; 24 | `) 25 | if err != nil { 26 | return nil, nil, err 27 | } 28 | 29 | achvdCond := 0 30 | interProto := make([]string, 0, 10) 31 | unionProto := make([]string, 0, 10) 32 | iterProv := make([][]string, len(iters)) 33 | 34 | for i := range iters { 35 | 36 | // Request all rule labels as long as the 37 | // execution eventually achieved its condition. 38 | condRules, err := stmtCondRules.QueryNeo(map[string]interface{}{ 39 | "run": (1000 + iters[i]), 40 | "condition": condition, 41 | }) 42 | if err != nil { 43 | return nil, nil, err 44 | } 45 | 46 | condAllRules, _, err := condRules.All() 47 | if err != nil { 48 | return nil, nil, err 49 | } 50 | 51 | err = condRules.Close() 52 | if err != nil { 53 | return nil, nil, err 54 | } 55 | 56 | for j := range condAllRules { 57 | 58 | for k := range condAllRules[j] { 59 | 60 | rulesRaw := condAllRules[j][k].([]interface{}) 61 | rules := make([]string, len(rulesRaw)) 62 | 63 | for l := range rules { 64 | rules[l] = rulesRaw[l].(string) 65 | } 66 | 67 | if len(rules) > 0 { 68 | 69 | // Count how many times the antecedent was achieved. 70 | achvdCond += 1 71 | 72 | // Add rules slice to tracking structure. 73 | iterProv[i] = rules 74 | } 75 | } 76 | } 77 | } 78 | 79 | // Initially, set first chain as longest. 80 | longest := len(iterProv[0]) 81 | 82 | for i := range iterProv[0] { 83 | 84 | foundIn := 1 85 | 86 | for j := 1; j < len(iterProv); j++ { 87 | 88 | if len(iterProv[j]) > 0 { 89 | 90 | for k := range iterProv[j] { 91 | 92 | // If found, mark label as part of the intersection. 93 | if iterProv[0][i] == iterProv[j][k] { 94 | foundIn++ 95 | } 96 | } 97 | } 98 | 99 | // Update longest if necessary. 100 | if len(iterProv[j]) > longest { 101 | longest = len(iterProv[j]) 102 | } 103 | } 104 | 105 | // If in intersection, append label to final prototype. 106 | if (foundIn == achvdCond) && (iterProv[0][i] != condition) { 107 | interProto = append(interProto, iterProv[0][i]) 108 | } 109 | } 110 | 111 | // Keep track of rules we already saw. 112 | alreadySeen := make(map[string]bool) 113 | 114 | for i := 0; i < longest; i++ { 115 | 116 | for j := range iterProv { 117 | 118 | if i < len(iterProv[j]) { 119 | 120 | if !alreadySeen[iterProv[j][i]] && (iterProv[j][i] != condition) { 121 | 122 | // New label, add to union. 123 | unionProto = append(unionProto, iterProv[j][i]) 124 | 125 | // Update map to seen for this label. 126 | alreadySeen[iterProv[j][i]] = true 127 | } 128 | } 129 | } 130 | } 131 | 132 | err = stmtCondRules.Close() 133 | if err != nil { 134 | return nil, nil, err 135 | } 136 | 137 | return interProto, unionProto, nil 138 | } 139 | 140 | // missingFrom 141 | func (n *Neo4J) missingFrom(proto []string, failedIter uint, condition string) ([]string, error) { 142 | 143 | stmtMissRules, err := n.Conn1.PrepareNeo(` 144 | MATCH (r:Rule {run: {run}, condition: {condition}}) 145 | WITH collect(DISTINCT r.table) AS rules 146 | RETURN rules; 147 | `) 148 | if err != nil { 149 | return nil, err 150 | } 151 | 152 | missRules, err := stmtMissRules.QueryNeo(map[string]interface{}{ 153 | "run": (1000 + failedIter), 154 | "condition": condition, 155 | }) 156 | if err != nil { 157 | return nil, err 158 | } 159 | 160 | missAllRules, _, err := missRules.All() 161 | if err != nil { 162 | return nil, err 163 | } 164 | 165 | err = missRules.Close() 166 | if err != nil { 167 | return nil, err 168 | } 169 | 170 | failedRules := make(map[string]bool) 171 | 172 | for j := range missAllRules { 173 | 174 | for k := range missAllRules[j] { 175 | 176 | rulesRaw := missAllRules[j][k].([]interface{}) 177 | rules := make([]string, len(rulesRaw)) 178 | 179 | for l := range rules { 180 | 181 | rules[l] = rulesRaw[l].(string) 182 | 183 | // Add rule to tracking structure. 184 | failedRules[rules[l]] = true 185 | } 186 | } 187 | } 188 | 189 | // Figure out the difference in rules 190 | // between prototype and failed run's rules. 191 | missing := make([]string, 0, 3) 192 | 193 | for p := range proto { 194 | 195 | if !failedRules[proto[p]] { 196 | missing = append(missing, fmt.Sprintf("%s", proto[p])) 197 | } 198 | } 199 | 200 | err = stmtMissRules.Close() 201 | if err != nil { 202 | return nil, err 203 | } 204 | 205 | return missing, nil 206 | } 207 | 208 | // CreatePrototypes 209 | func (n *Neo4J) CreatePrototypes(iters []uint, failedIters []uint) ([]string, [][]string, []string, [][]string, error) { 210 | 211 | fmt.Printf("Running extraction of success prototypes... ") 212 | 213 | // In the future, we might want to add 214 | // analysis of antecedent prototypes. 215 | 216 | // Create consequent intersection-prototype 217 | // and union-prototype. 218 | interProto, unionProto, err := n.extractProtos(iters, "post") 219 | if err != nil { 220 | return nil, nil, nil, nil, err 221 | } 222 | 223 | interProtoMiss := make([][]string, len(failedIters)) 224 | unionProtoMiss := make([][]string, len(failedIters)) 225 | 226 | for i := range failedIters { 227 | 228 | // Collect all nodes missing in the failed execution's consequent 229 | // provenance that are part of the intersection-prototype. 230 | interMiss, err := n.missingFrom(interProto, failedIters[i], "post") 231 | if err != nil { 232 | return nil, nil, nil, nil, err 233 | } 234 | interProtoMiss[i] = interMiss 235 | 236 | // Collect all nodes missing in the failed execution's consequent 237 | // provenance that are part of the union-prototype. 238 | unionMiss, err := n.missingFrom(unionProto, failedIters[i], "post") 239 | if err != nil { 240 | return nil, nil, nil, nil, err 241 | } 242 | unionProtoMiss[i] = unionMiss 243 | } 244 | 245 | for i := range interProto { 246 | interProto[i] = fmt.Sprintf("%s", interProto[i]) 247 | } 248 | 249 | for i := range unionProto { 250 | unionProto[i] = fmt.Sprintf("%s", unionProto[i]) 251 | } 252 | 253 | fmt.Printf("done\n\n") 254 | 255 | return interProto, interProtoMiss, unionProto, unionProtoMiss, nil 256 | } 257 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "encoding/json" 10 | "io/ioutil" 11 | "path/filepath" 12 | 13 | "github.com/awalterschulze/gographviz" 14 | fi "github.com/numbleroot/nemo/faultinjectors" 15 | gr "github.com/numbleroot/nemo/graphing" 16 | re "github.com/numbleroot/nemo/report" 17 | ) 18 | 19 | // Interfaces. 20 | 21 | // FaultInjector 22 | type FaultInjector interface { 23 | LoadOutput() error 24 | GetFailureSpec() *fi.FailureSpec 25 | GetMsgsFailedRuns() [][]*fi.Message 26 | GetOutput() []*fi.Run 27 | GetRunsIters() []uint 28 | GetSuccessRunsIters() []uint 29 | GetFailedRunsIters() []uint 30 | } 31 | 32 | // GraphDatabase 33 | type GraphDatabase interface { 34 | InitGraphDB(string, []*fi.Run) error 35 | CloseDB() error 36 | LoadRawProvenance() error 37 | SimplifyProv([]uint) error 38 | CreateHazardAnalysis(string) ([]*gographviz.Graph, error) 39 | CreatePrototypes([]uint, []uint) ([]string, [][]string, []string, [][]string, error) 40 | PullPrePostProv() ([]*gographviz.Graph, []*gographviz.Graph, []*gographviz.Graph, []*gographviz.Graph, error) 41 | CreateNaiveDiffProv(bool, []uint, *gographviz.Graph) ([]*gographviz.Graph, []*gographviz.Graph, [][]*fi.Missing, error) 42 | GenerateCorrections() ([]string, error) 43 | GenerateExtensions() (bool, []string, error) 44 | } 45 | 46 | // Reporter 47 | type Reporter interface { 48 | Prepare(string, string, string) error 49 | GenerateFigure(string, *gographviz.Graph) error 50 | GenerateFigures([]uint, string, []*gographviz.Graph) error 51 | } 52 | 53 | // Structs. 54 | 55 | // DebugRun 56 | type DebugRun struct { 57 | workDir string 58 | allResultsDir string 59 | thisResultsDir string 60 | faultInj FaultInjector 61 | graphDB GraphDatabase 62 | reporter Reporter 63 | } 64 | 65 | func main() { 66 | 67 | // Define which flags are supported. 68 | faultInjOutFlag := flag.String("faultInjOut", "", "Specify file system path to output directory of fault injector.") 69 | graphDBConnFlag := flag.String("graphDBConn", "bolt://127.0.0.1:7687", "Supply connection URI to dockerized graph database.") 70 | flag.Parse() 71 | 72 | // Extract and check for existence of required ones. 73 | faultInjOut := *faultInjOutFlag 74 | if faultInjOut == "" { 75 | log.Fatal("Please provide a fault injection output directory to analyze.") 76 | } 77 | 78 | graphDBConn := *graphDBConnFlag 79 | 80 | // Determine current working directory. 81 | curDir, err := filepath.Abs(".") 82 | if err != nil { 83 | log.Fatalf("Failed obtaining absolute current directory: %v", err) 84 | } 85 | 86 | // Start building structs. 87 | debugRun := &DebugRun{ 88 | workDir: curDir, 89 | allResultsDir: filepath.Join(curDir, "results"), 90 | thisResultsDir: filepath.Join(curDir, "results", filepath.Base(faultInjOut)), 91 | faultInj: &fi.Molly{ 92 | Run: filepath.Base(faultInjOut), 93 | OutputDir: faultInjOut, 94 | }, 95 | graphDB: &gr.Neo4J{}, 96 | reporter: &re.Report{}, 97 | } 98 | 99 | // Ensure the results directory for this debug run exists. 100 | err = os.MkdirAll(debugRun.allResultsDir, 0755) 101 | if err != nil { 102 | log.Fatalf("Could not ensure resDir exists: %v", err) 103 | } 104 | 105 | // Extract, transform, and load fault injector output. 106 | err = debugRun.faultInj.LoadOutput() 107 | if err != nil { 108 | log.Fatalf("Failed to load output from Molly: %v", err) 109 | } 110 | 111 | // Graph queries. 112 | 113 | // Determine the IDs of all and all failed executions. 114 | iters := debugRun.faultInj.GetRunsIters() 115 | failedIters := debugRun.faultInj.GetFailedRunsIters() 116 | 117 | // Connect to graph database docker container. 118 | err = debugRun.graphDB.InitGraphDB(graphDBConn, debugRun.faultInj.GetOutput()) 119 | if err != nil { 120 | log.Fatalf("Failed to initialize connection to graph database: %v", err) 121 | } 122 | defer debugRun.graphDB.CloseDB() 123 | 124 | // Load initial (naive) version of provenance 125 | // graphs for antecedent and consequent. 126 | err = debugRun.graphDB.LoadRawProvenance() 127 | if err != nil { 128 | log.Fatalf("Failed to import provenance (naive) into graph database: %v", err) 129 | } 130 | 131 | // Clean-up loaded provenance data and 132 | // re-import in reduced versions. 133 | err = debugRun.graphDB.SimplifyProv(iters) 134 | if err != nil { 135 | log.Fatalf("Could not clean-up initial provenance data: %v", err) 136 | } 137 | 138 | // Create hazard analysis DOT figure. 139 | hazardDots, err := debugRun.graphDB.CreateHazardAnalysis(faultInjOut) 140 | if err != nil { 141 | log.Fatalf("Failed to perform hazard analysis of simulation: %v", err) 142 | } 143 | 144 | // Extract prototypes of successful and 145 | // failed runs (skeletons) and import. 146 | interProto, interProtoMiss, unionProto, unionProtoMiss, err := debugRun.graphDB.CreatePrototypes(debugRun.faultInj.GetSuccessRunsIters(), debugRun.faultInj.GetFailedRunsIters()) 147 | if err != nil { 148 | log.Fatalf("Failed to create prototypes of successful executions: %v", err) 149 | } 150 | 151 | // Pull antecedent and consequent provenance 152 | // and create DOT diagram strings. 153 | preProvDots, postProvDots, preCleanProvDots, postCleanProvDots, err := debugRun.graphDB.PullPrePostProv() 154 | if err != nil { 155 | log.Fatalf("Failed to pull and generate antecedent and consequent provenance DOT: %v", err) 156 | } 157 | 158 | // Create differential provenance graphs for 159 | // consequent provenance. 160 | naiveDiffDots, naiveFailedDots, missingEvents, err := debugRun.graphDB.CreateNaiveDiffProv(false, debugRun.faultInj.GetFailedRunsIters(), postProvDots[0]) 161 | if err != nil { 162 | log.Fatalf("Could not create differential provenance between successful and failed provenance: %v", err) 163 | } 164 | 165 | var corrections []string 166 | if len(failedIters) > 0 { 167 | 168 | // Generate correction suggestions for moving towards correctness. 169 | corrections, err = debugRun.graphDB.GenerateCorrections() 170 | if err != nil { 171 | log.Fatalf("Error while generating corrections: %v", err) 172 | } 173 | } 174 | 175 | // Attempt to create extension proposals in case 176 | // the antecedent depends on network events. 177 | allRunsAchievedPre, extensions, err := debugRun.graphDB.GenerateExtensions() 178 | if err != nil { 179 | log.Fatalf("Error while generating extensions: %v", err) 180 | } 181 | 182 | // Reporting. 183 | 184 | // Retrieve current state of run output. 185 | // Enrich with missing events. 186 | 187 | runs := debugRun.faultInj.GetOutput() 188 | for i := range iters { 189 | 190 | // Progressively formulate one top-level recommendation 191 | // for programmers to focus on first. 192 | if len(corrections) > 0 { 193 | 194 | // We observed an specification violation. Suggest corrections first. 195 | runs[iters[i]].Recommendation = append(runs[iters[i]].Recommendation, "A fault occurred. Let's try making the protocol correct first.") 196 | runs[iters[i]].Recommendation = append(runs[iters[i]].Recommendation, corrections...) 197 | } else if len(extensions) > 0 { 198 | 199 | // In case there exist runs in this execution where the 200 | // antecedent was not achieved (not per se a problem!) 201 | // and communication had to be performed for the successful 202 | // run to establish the antecedent, it might be a good 203 | // idea for the system designers to make sure these rules 204 | // are maximum fault-tolerant. 205 | runs[iters[i]].Recommendation = append(runs[iters[i]].Recommendation, "Good job, no specification violation. At least one run did not establish the antecedent, though. Maybe double-check the fault tolerance of the following rules:") 206 | runs[iters[i]].Recommendation = append(runs[iters[i]].Recommendation, extensions...) 207 | } else if !allRunsAchievedPre { 208 | 209 | // We saw a bug, but we don't find corrections or extensions 210 | // to suggest. This must be a bug outside our capabilities 211 | // (e.g., local-logic). 212 | runs[iters[i]].Recommendation = append(runs[iters[i]].Recommendation, "Nemo can't help with this type of bug. Please use the graphs below regarding differential provenance for guidance to root cause.") 213 | } else { 214 | 215 | // No specification violation happened, no more fault tolerance to add. 216 | runs[iters[i]].Recommendation = append(runs[iters[i]].Recommendation, "Well done! No faults, no missing fault tolerance.") 217 | } 218 | 219 | runs[iters[i]].InterProto = interProto 220 | runs[iters[i]].UnionProto = unionProto 221 | } 222 | 223 | j := 0 224 | for i := range failedIters { 225 | runs[failedIters[i]].Corrections = corrections 226 | runs[failedIters[i]].MissingEvents = missingEvents[j] 227 | runs[failedIters[i]].InterProtoMissing = interProtoMiss[j] 228 | runs[failedIters[i]].UnionProtoMissing = unionProtoMiss[j] 229 | j++ 230 | } 231 | 232 | // Marshal collected debugging information to JSON. 233 | debuggingJSON, err := json.Marshal(runs) 234 | if err != nil { 235 | log.Fatalf("Failed to marshal debugging information to JSON: %v", err) 236 | } 237 | 238 | // Prepare report webpage containing all insights and suggestions. 239 | err = debugRun.reporter.Prepare(debugRun.workDir, debugRun.allResultsDir, debugRun.thisResultsDir) 240 | if err != nil { 241 | log.Fatalf("Failed to prepare debugging report: %v", err) 242 | } 243 | 244 | // Write debugging JSON to file 'debugging.json'. 245 | err = ioutil.WriteFile(filepath.Join(debugRun.thisResultsDir, "debugging.json"), debuggingJSON, 0644) 246 | if err != nil { 247 | log.Fatalf("Error writing out debugging.json: %v", err) 248 | } 249 | 250 | // Generate and write-out hazard analysis figures. 251 | err = debugRun.reporter.GenerateFigures(iters, "spacetime", hazardDots) 252 | if err != nil { 253 | log.Fatalf("Could not generate hazard analysis figures for report: %v", err) 254 | } 255 | 256 | // Generate and write-out antecedent provenance figures. 257 | err = debugRun.reporter.GenerateFigures(iters, "pre_prov", preProvDots) 258 | if err != nil { 259 | log.Fatalf("Could not generate antecedent provenance figures for report: %v", err) 260 | } 261 | 262 | // Generate and write-out consequent provenance figures. 263 | err = debugRun.reporter.GenerateFigures(iters, "post_prov", postProvDots) 264 | if err != nil { 265 | log.Fatalf("Could not generate consequent provenance figures for report: %v", err) 266 | } 267 | 268 | // Generate and write-out cleaned-up antecedent provenance figures. 269 | err = debugRun.reporter.GenerateFigures(iters, "pre_prov_clean", preCleanProvDots) 270 | if err != nil { 271 | log.Fatalf("Could not generate cleaned-up antecedent provenance figures for report: %v", err) 272 | } 273 | 274 | // Generate and write-out cleaned-up consequent provenance figures. 275 | err = debugRun.reporter.GenerateFigures(iters, "post_prov_clean", postCleanProvDots) 276 | if err != nil { 277 | log.Fatalf("Could not generate cleaned-up consequent provenance figures for report: %v", err) 278 | } 279 | 280 | // Generate and write-out naive differential provenance (diff) figures. 281 | err = debugRun.reporter.GenerateFigures(failedIters, "diff_post_prov-diff", naiveDiffDots) 282 | if err != nil { 283 | log.Fatalf("Could not generate naive differential provenance (diff) figures for report: %v", err) 284 | } 285 | 286 | // Generate and write-out naive differential provenance (failed) figures. 287 | err = debugRun.reporter.GenerateFigures(failedIters, "diff_post_prov-failed", naiveFailedDots) 288 | if err != nil { 289 | log.Fatalf("Could not generate naive differential provenance (failed) figures for report: %v", err) 290 | } 291 | 292 | fmt.Printf("All done! Find the debug report here: %s\n\n", filepath.Join(debugRun.thisResultsDir, "index.html")) 293 | } 294 | -------------------------------------------------------------------------------- /report/assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Nemo - Debugging Results 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 |

Runs

20 | Click on a run to see more information. 21 | 22 |
23 | 24 |
25 | 26 |
27 | 28 |
29 | 30 |
31 | 32 |

Recommendation

33 | Nemo analyzed your protocol and recommends to make the following changes. 34 | 35 |
36 | 37 |
    38 | 39 |
    40 | 41 |
    42 | 43 |
    44 | 45 |
    46 | 47 |
    48 | 49 |
    50 | 51 |
    52 | 53 |
    54 | 55 |
    56 | 57 |
    58 | 59 | Can we suggest modifications to antecedent events that make the antecedent stricter? 60 | 61 |
      62 | 63 |
      64 | 65 |
      66 | 67 |
      68 | 69 |
      70 | 71 |
      72 | 73 |
      74 | 75 |
      76 | 77 |
      78 | 79 |
      80 | 81 |
      82 | 83 | Which events are missing from the bad execution compared to the good one? Frontier elements are bordered dashed red. 84 | 85 |
      86 | 87 |
      Rule    needs to fire to achieve success, but the following events are not taking place:
      88 |
        89 | 90 |
        91 | 92 |
        93 | 94 |
        95 | 96 |
        97 | 98 | 99 |
        100 | 101 |
        102 | 103 | 104 |
        105 | 106 |
        107 | 108 | 109 |
        110 | 111 |
        112 | 113 |
        114 | 115 |
        116 | 117 |
        118 | 119 |
        120 | 121 |
        122 | 123 |
        124 | 125 |
        126 | 127 |
        128 | 129 |
        130 | 131 |
        132 | 133 |
        134 | 135 |
        136 | 137 | When do antecedent and consequent hold? If a window exists between antecedent and consequent, this allows for faults to happen. 138 | 139 |
        140 | 141 |
        142 | 143 |
        144 | 145 |
        146 | 147 |
        148 | 149 |
        150 | 151 |
        152 | 153 |
        154 | 155 |
        156 | 157 |
        158 | 159 |
        160 | 161 | Which events were required for antecedent and consequent to be achieved? 162 | Message passing events are marked with a bold border, next chains written in gold. 163 | 164 |
        165 | 166 |
        167 | 168 |

        Pre

        169 | 170 |
        171 | 172 |
        173 | 174 |
        175 | 176 |

        Post

        177 | 178 |
        179 | 180 |
        181 | 182 |
        183 | 184 |
        185 | 186 |
        187 | 188 |
        189 | 190 |
        191 | 192 |
        193 | 194 |
        195 | 196 |
        197 | 198 |
        199 | 200 |
        201 | 202 |
        203 | 204 | Which events were essentially required for antecedent and consequent to be achieved? 205 | Message passing events are marked with a bold border, next chains written in gold. 206 | 207 |
        208 | 209 |
        210 | 211 |

        Simplified Pre

        212 | 213 |
        214 | 215 |
        216 | 217 |
        218 | 219 |

        Simplified Post

        220 | 221 |
        222 | 223 |
        224 | 225 |
        226 | 227 |
        228 | 229 |
        230 | 231 |
        232 | 233 |
        234 | 235 |
        236 | 237 |
        238 | 239 |
        240 | 241 |
        242 | 243 |
        244 | 245 |
        246 | 247 |
        248 | 249 |
        250 | 251 | Which rules take place in all executions (intersection of all successful runs)? 252 |
          253 | 254 |
          255 | 256 |
          257 | 258 | If failed: which rules are certainly missing from this execution? 259 |
            260 | 261 |
            262 | 263 |
            264 | 265 |
            266 | 267 |
            268 | 269 |
            270 | 271 |
            272 | 273 |
            274 | 275 |
            276 | 277 |
            278 | 279 |
            280 | 281 |
            282 | 283 |
            284 | 285 |
            286 | 287 |
            288 | 289 | Which rules could possibly take place in a successful execution (union of all successful runs)? 290 |
              291 | 292 |
              293 | 294 |
              295 | 296 | If failed: which rules—if added—could turn this execution into a succesful one? 297 |
                298 | 299 |
                300 | 301 |
                302 | 303 |
                304 | 305 |
                306 | 307 |
                308 | 309 |
                310 | 311 | 312 | 313 | 314 | 315 | 316 | 530 | 531 | 532 | -------------------------------------------------------------------------------- /report/assets/vendor/fontawesome-all.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.0.10 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | .fa,.fab,.fal,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{animation:a 2s infinite linear}.fa-pulse{animation:a 1s infinite steps(8)}@keyframes a{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-aws:before{content:"\f375"}.fa-backward:before{content:"\f04a"}.fa-balance-scale:before{content:"\f24e"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bicycle:before{content:"\f206"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blind:before{content:"\f29d"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-btc:before{content:"\f15a"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-certificate:before{content:"\f0a3"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-square:before{content:"\f14a"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-credit-card:before{content:"\f09d"}.fa-crop:before{content:"\f125"}.fa-crosshairs:before{content:"\f05b"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-deviantart:before{content:"\f1bd"}.fa-diagnoses:before{content:"\f470"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drupal:before{content:"\f1a9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-fax:before{content:"\f1ac"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-excel:before{content:"\f1c3"}.fa-file-image:before{content:"\f1c5"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fire:before{content:"\f06d"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-firstdraft:before{content:"\f3a1"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frown:before{content:"\f119"}.fa-futbol:before{content:"\f1e3"}.fa-gamepad:before{content:"\f11b"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-gift:before{content:"\f06b"}.fa-git:before{content:"\f1d3"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-martini:before{content:"\f000"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-handshake:before{content:"\f2b5"}.fa-hashtag:before{content:"\f292"}.fa-hdd:before{content:"\f0a0"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-heart:before{content:"\f004"}.fa-heartbeat:before{content:"\f21e"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-houzz:before{content:"\f27c"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-internet-explorer:before{content:"\f26b"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jenkins:before{content:"\f3b6"}.fa-joget:before{content:"\f3b7"}.fa-joomla:before{content:"\f1aa"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-key:before{content:"\f084"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-korvue:before{content:"\f42f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-male:before{content:"\f183"}.fa-map:before{content:"\f279"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-maxcdn:before{content:"\f136"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-meh:before{content:"\f11a"}.fa-mercury:before{content:"\f223"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-moon:before{content:"\f186"}.fa-motorcycle:before{content:"\f21c"}.fa-mouse-pointer:before{content:"\f245"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nintendo-switch:before{content:"\f418"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-osi:before{content:"\f41a"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-paint-brush:before{content:"\f1fc"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-people-carry:before{content:"\f4ce"}.fa-percent:before{content:"\f295"}.fa-periscope:before{content:"\f3da"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phone:before{content:"\f095"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-volume:before{content:"\f2a0"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-plane:before{content:"\f072"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poo:before{content:"\f2fe"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-random:before{content:"\f074"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-rendact:before{content:"\f3e4"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-resolving:before{content:"\f3e7"}.fa-retweet:before{content:"\f079"}.fa-ribbon:before{content:"\f4d6"}.fa-road:before{content:"\f018"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-rupee-sign:before{content:"\f156"}.fa-safari:before{content:"\f267"}.fa-sass:before{content:"\f41e"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-scribd:before{content:"\f28a"}.fa-search:before{content:"\f002"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shower:before{content:"\f2cc"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smoking:before{content:"\f48d"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowflake:before{content:"\f2dc"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-spinner:before{content:"\f110"}.fa-spotify:before{content:"\f1bc"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-star:before{content:"\f005"}.fa-star-half:before{content:"\f089"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-strava:before{content:"\f428"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-trademark:before{content:"\f25c"}.fa-train:before{content:"\f238"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-moving:before{content:"\f4df"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-uikit:before{content:"\f403"}.fa-umbrella:before{content:"\f0e9"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-circle:before{content:"\f2bd"}.fa-user-md:before{content:"\f0f0"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vuejs:before{content:"\f41f"}.fa-warehouse:before{content:"\f494"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-glass:before{content:"\f4e3"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:Font Awesome\ 5 Brands;font-style:normal;font-weight:400;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:Font Awesome\ 5 Brands}@font-face{font-family:Font Awesome\ 5 Free;font-style:normal;font-weight:400;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-weight:400}@font-face{font-family:Font Awesome\ 5 Free;font-style:normal;font-weight:900;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.far,.fas{font-family:Font Awesome\ 5 Free}.fa,.fas{font-weight:900} -------------------------------------------------------------------------------- /report/assets/vendor/nemo.css: -------------------------------------------------------------------------------- 1 | h1, h2, h3, h4 { margin: 10px 0 15px; } 2 | 3 | img { 4 | display: block; 5 | width: 100%; 6 | height: auto; 7 | margin: 0 auto; 8 | } 9 | 10 | body pre, body code { font-size: 100%; } 11 | .accordion { margin-bottom: 50px; } 12 | .container-fluid { margin: 50px auto; } 13 | .row { margin: 0 0 50px; } 14 | .card-header h5 button { font-size: 1.35rem; } 15 | 16 | .help-block { 17 | display: block; 18 | margin-bottom: 15px; 19 | } 20 | 21 | #runs-table { width: 100%; } 22 | #recommendation li { margin: 0 0 10px; } 23 | 24 | #recommendation pre { 25 | display: inline; 26 | font-weight: bold; 27 | } 28 | 29 | #hazard-analysis img { width: 50%; } 30 | 31 | .anchor { 32 | position: relative; 33 | width: 100%; 34 | height: 1000px; 35 | } 36 | 37 | .diff-prov-checker { 38 | position: absolute; 39 | z-index: 3; 40 | top: 0; 41 | left: 0; 42 | } 43 | 44 | #good-bad-diff-prov img { 45 | position: absolute; 46 | top: 0; 47 | left: 0; 48 | } 49 | 50 | #good-bad-diff-prov img.low { z-index: 0; } 51 | #good-bad-diff-prov img.medium { z-index: 1; } 52 | #good-bad-diff-prov img.top { z-index: 2; } 53 | 54 | #pre-post-correctness-corrections pre { 55 | display: inline; 56 | font-weight: bold; 57 | } 58 | -------------------------------------------------------------------------------- /report/assets/webfonts/fa-brands-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbleroot/nemo/18fe0a36a2f647cc15f58eb866a148a79ba64bfb/report/assets/webfonts/fa-brands-400.eot -------------------------------------------------------------------------------- /report/assets/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbleroot/nemo/18fe0a36a2f647cc15f58eb866a148a79ba64bfb/report/assets/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /report/assets/webfonts/fa-brands-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbleroot/nemo/18fe0a36a2f647cc15f58eb866a148a79ba64bfb/report/assets/webfonts/fa-brands-400.woff -------------------------------------------------------------------------------- /report/assets/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbleroot/nemo/18fe0a36a2f647cc15f58eb866a148a79ba64bfb/report/assets/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /report/assets/webfonts/fa-regular-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbleroot/nemo/18fe0a36a2f647cc15f58eb866a148a79ba64bfb/report/assets/webfonts/fa-regular-400.eot -------------------------------------------------------------------------------- /report/assets/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbleroot/nemo/18fe0a36a2f647cc15f58eb866a148a79ba64bfb/report/assets/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /report/assets/webfonts/fa-regular-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbleroot/nemo/18fe0a36a2f647cc15f58eb866a148a79ba64bfb/report/assets/webfonts/fa-regular-400.woff -------------------------------------------------------------------------------- /report/assets/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbleroot/nemo/18fe0a36a2f647cc15f58eb866a148a79ba64bfb/report/assets/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /report/assets/webfonts/fa-solid-900.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbleroot/nemo/18fe0a36a2f647cc15f58eb866a148a79ba64bfb/report/assets/webfonts/fa-solid-900.eot -------------------------------------------------------------------------------- /report/assets/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbleroot/nemo/18fe0a36a2f647cc15f58eb866a148a79ba64bfb/report/assets/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /report/assets/webfonts/fa-solid-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbleroot/nemo/18fe0a36a2f647cc15f58eb866a148a79ba64bfb/report/assets/webfonts/fa-solid-900.woff -------------------------------------------------------------------------------- /report/assets/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbleroot/nemo/18fe0a36a2f647cc15f58eb866a148a79ba64bfb/report/assets/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /report/helpers.go: -------------------------------------------------------------------------------- 1 | package report 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "os/exec" 8 | ) 9 | 10 | // Functions. 11 | 12 | // copyDir 13 | func copyDir(srcDir string, resDir string) error { 14 | 15 | fmt.Printf("Copying %s to %s...", srcDir, resDir) 16 | cmd := exec.Command("cp", "-r", srcDir, resDir) 17 | out, err := cmd.CombinedOutput() 18 | if err != nil { 19 | return err 20 | } 21 | 22 | if strings.TrimSpace(string(out)) != "" { 23 | return fmt.Errorf("Wrong return value from copy command for directory: %s", out) 24 | } 25 | fmt.Printf(" done\n\n") 26 | 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /report/webpage.go: -------------------------------------------------------------------------------- 1 | package report 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "io/ioutil" 9 | "os/exec" 10 | "path/filepath" 11 | 12 | "github.com/awalterschulze/gographviz" 13 | ) 14 | 15 | // Structs. 16 | 17 | // Report 18 | type Report struct { 19 | resDir string 20 | figuresDir string 21 | } 22 | 23 | // Functions. 24 | 25 | // Prepare 26 | func (r *Report) Prepare(wrkDir string, allResDir string, thisResDir string) error { 27 | 28 | r.resDir = thisResDir 29 | r.figuresDir = filepath.Join(thisResDir, "figures") 30 | 31 | // Copy webpage template to result directory. 32 | err := copyDir(filepath.Join(wrkDir, "report", "assets"), allResDir) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | // Rename to final results directory name. 38 | err = os.Rename(filepath.Join(allResDir, "assets"), thisResDir) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | // Create directory to hold diagrams. 44 | err = os.MkdirAll(r.figuresDir, 0755) 45 | if err != nil { 46 | return err 47 | } 48 | 49 | return nil 50 | } 51 | 52 | // GenerateFigure renders a supplied dot graph. 53 | func (r *Report) GenerateFigure(fileName string, dotProv *gographviz.Graph) error { 54 | 55 | dotFilePath := filepath.Join(r.figuresDir, fmt.Sprintf("%s.dot", fileName)) 56 | svgFilePath := filepath.Join(r.figuresDir, fmt.Sprintf("%s.svg", fileName)) 57 | 58 | // Write-out file containing DOT string. 59 | err := ioutil.WriteFile(dotFilePath, []byte(dotProv.String()), 0644) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | // Run SVG generator on DOT file. 65 | cmd := exec.Command("dot", "-Tsvg", "-o", svgFilePath, dotFilePath) 66 | out, err := cmd.CombinedOutput() 67 | if err != nil { 68 | return err 69 | } 70 | 71 | if strings.TrimSpace(string(out)) != "" { 72 | return fmt.Errorf("Wrong return value from SVG generation command: %s", out) 73 | } 74 | 75 | return nil 76 | } 77 | 78 | // GenerateFigures 79 | func (r *Report) GenerateFigures(iters []uint, name string, dotProvs []*gographviz.Graph) error { 80 | 81 | // We require that each element in dotProvs 82 | // has a corresponding element in names. 83 | if len(iters) != len(dotProvs) { 84 | return fmt.Errorf("Unequal number of iteration numbers and DOT graph strings") 85 | } 86 | 87 | for i := range iters { 88 | 89 | fileName := fmt.Sprintf("run_%d_%s", iters[i], name) 90 | 91 | // Generate and write-out figure. 92 | err := r.GenerateFigure(fileName, dotProvs[i]) 93 | if err != nil { 94 | return err 95 | } 96 | } 97 | 98 | return nil 99 | } 100 | --------------------------------------------------------------------------------