├── .gitignore
├── .project
├── .settings
├── org.eclipse.jdt.core.prefs
└── org.eclipse.m2e.core.prefs
├── .vscode
└── settings.json
├── LICENSE.TXT
├── README.md
├── SAMPLE.cypher
├── pom.xml
├── sample-report.html
└── src
└── main
├── java
└── org
│ └── mitre
│ └── neoprofiler
│ ├── NeoProfiler.java
│ ├── html
│ ├── HTMLMaker.java
│ └── package-info.java
│ ├── markdown
│ ├── MarkdownMaker.java
│ └── package-info.java
│ ├── package-info.java
│ ├── profile
│ ├── DBProfile.java
│ ├── LabelProfile.java
│ ├── LabelPropertyProfile.java
│ ├── NeoConstraint.java
│ ├── NeoProfile.java
│ ├── NeoProperty.java
│ ├── NodesProfile.java
│ ├── ParameterizedNeoProfile.java
│ ├── RelationshipTypeProfile.java
│ ├── RelationshipsProfile.java
│ ├── SchemaProfile.java
│ └── package-info.java
│ └── profiler
│ ├── LabelProfiler.java
│ ├── LabelPropertyProfiler.java
│ ├── NodesProfiler.java
│ ├── PathsProfiler.java
│ ├── Profiler.java
│ ├── QueryRunner.java
│ ├── RelationshipTypeProfiler.java
│ ├── RelationshipsProfiler.java
│ ├── SchemaProfiler.java
│ ├── UnlabeledNodeProfiler.java
│ └── package-info.java
└── resources
├── .shell_history
└── template.html
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | *~
3 | .classpath
4 | *.classpath
5 | .externalToolBuilders
6 | *.html
7 | .idea
8 | /bin
9 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | neoprofiler
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 | org.eclipse.m2e.core.maven2Builder
15 |
16 |
17 |
18 |
19 |
20 | org.eclipse.m2e.core.maven2Nature
21 | org.eclipse.jdt.core.javanature
22 |
23 |
24 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
5 | org.eclipse.jdt.core.compiler.compliance=1.7
6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate
7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate
8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate
9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
11 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
12 | org.eclipse.jdt.core.compiler.source=1.7
13 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.m2e.core.prefs:
--------------------------------------------------------------------------------
1 | activeProfiles=
2 | eclipse.preferences.version=1
3 | resolveWorkspaceProjects=true
4 | version=1
5 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "java.configuration.updateBuildConfiguration": "automatic"
3 | }
--------------------------------------------------------------------------------
/LICENSE.TXT:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Synopsis
2 |
3 | This package is a lightweight set of profiling utilities for Neo4J databases.
4 | This is intended to quickly help users derive the rough structure and
5 | population of a new Neo4J database.
6 |
7 | ## Quick Start
8 |
9 | **Clone the repo**: `git clone https://github.com/moxious/neoprofiler.git`
10 |
11 | **Package the application**: `mvn package`
12 |
13 | Note that packaging includes the creation of a runnable "One JAR" which includes all necessary dependencies.
14 |
15 | **Run from the command line**:
16 | `java -jar target/neoprofiler-0.15.jar -db path_to_db -format html -output db_profile_report.html`
17 |
18 | The argument given to the application should be the path to a directory containing a neo4j database.
19 |
20 | ## Usage
21 |
22 | ```
23 | usage: NeoProfiler
24 | -db Path to directory where neo4j database is located
25 | -format Output format: valid values are json, markdown, or
26 | html
27 | -output Name of output file to write; program will print to
28 | console if not specified.
29 | ```
30 |
31 | ## Motivation
32 |
33 | If you work with many Neo4J databases, it's often convenient to get a
34 | high-level picture of what is in a particular dataset before you jump in. If
35 | you write application code which interacts with a Neo4J database, it's good to
36 | be able to verify that the population of nodes and relationships you're writing
37 | have the properties that you expect.
38 |
39 | ## Output Example
40 |
41 | The program knows how to output JSON, Markdown, and HTML as output. To see
42 | a sample HTML report, download [sample-report.html](sample-report.html) and
43 | view it in a browser.
44 |
45 | ## Metagraphs
46 |
47 | One of the things that the profiler outputs is what we call a "metagraph". Neo4J
48 | database structures themselves can be described as a graph. The nodes in the metagraph
49 | describe what kinds of labeled nodes, relationships, and so on are in your database.
50 | The relationships in the metagraph outline how these things connect.
51 |
52 | Here's an example of a [metagraph](https://imgur.com/qsPvQFY).
53 |
54 |
55 |
56 | Notice that there are lighter grey and darker grey nodes; the light grey nodes whose label
57 | starts with "REL" are actually referring to relationships in the neo4j database. This
58 | graph indicates that nodes labeled "emp" can have a "leads_project" relationship to nodes
59 | labeled "project". Additionally, "project" nodes themselves can have a "leads_project"
60 | relationship to other "project" nodes.
61 |
62 | Nodes labeled "dept" can have "part_of" relationships back to other "dept" nodes; this is
63 | what a hierarchy might look like as expressed via a metagraph.
64 |
65 | A longer discussion of metagraphs can be [found here](http://gist.neo4j.org/?8640853).
66 |
67 | ## FAQ
68 |
69 | ### How does it work?
70 |
71 | The program contains a number of profilers, most of which run very simple
72 | Cypher queries against your database and provide summary statistics. Some
73 | profilers will actually discover data in your graph and then spawn other
74 | profilers which will run later. For example, if a label called "Person" is
75 | discovered in the data, a label profiler will be added to the run queue to
76 | inspect the population of nodes with that label.
77 |
78 | Right now, the focus is on simple queries that return descriptive statistics.
79 |
80 | Additionally, profiling right now must occur while the database is offline,
81 | because the profiler opens the database in embedded mode.
82 |
83 | ### Can I profile a database available via a URL?
84 |
85 | No, not yet - but this is part of the plan, and is in the works.
86 |
87 | ### Will the profiler modify my database?
88 |
89 | Absolutely not. All queries are read-only; any storage that is needed will
90 | happen in memory or via another method, to guarantee no modifications are
91 | made to the database being profiled.
92 |
93 | ### What version of Neo4J Does it use? ###
94 |
95 | At present, the code trunk uses 2.1.2. Because of some non-backwards compatible
96 | database format changes, this may require that you upgrade older databases to
97 | the format that 2.1 expects.
98 |
99 |
--------------------------------------------------------------------------------
/SAMPLE.cypher:
--------------------------------------------------------------------------------
1 | // This is a sample cypher file intended to create a trivial small
2 | // database for testing purposes.
3 | //
4 | // The sample database contains a few individuals, and a few relationships
5 | // amongst them, for the profiler to discover and report on.
6 |
7 | create (p:Person {name:"Bob", age:35, gender:"M" })
8 | -[:KNOWS]->(m:Person {name:"Susan", age:43, gender:"F"});
9 | create (p:Person {name:"Sam", age:23, gender:"M" })
10 | -[:KNOWS]->(m:Person {name:"Michael", age:22, gender:"M"});
11 |
12 | match (boss:Person {name:"Susan"}), (emp:Person {name:"Sam"}),
13 | (emp2:Person {name:"Michael"})
14 | create emp-[:WORKS_FOR]->boss, emp2-[:WORKS_FOR]->boss;
15 |
16 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | org.mitre
5 | neoprofiler
6 | 0.15
7 | Neo4J Database Profiler
8 | Software which can examine Neo4J databases and build descriptions of their contents.
9 |
10 |
11 | org.neo4j
12 | neo4j
13 | ${neo4j-version}
14 |
15 |
16 | org.neo4j
17 | neo4j-cypher
18 | ${neo4j-version}
19 |
20 |
21 | com.google.code.gson
22 | gson
23 | 2.8.2
24 |
25 |
26 | commons-cli
27 | commons-cli
28 | 1.4
29 |
30 |
31 | org.apache.velocity
32 | velocity
33 | 1.7
34 |
35 |
36 | org.neo4j.driver
37 | neo4j-java-driver
38 | 1.4.4
39 |
40 |
41 | org.springframework.boot
42 | spring-boot-starter-web
43 | 1.4.2.RELEASE
44 |
45 |
46 |
47 |
48 |
49 |
50 | org.springframework.boot
51 | spring-boot-maven-plugin
52 | 1.4.2.RELEASE
53 |
54 | true
55 | org.mitre.neoprofiler.NeoProfiler
56 | ZIP
57 |
58 |
59 |
60 |
61 | repackage
62 |
63 |
64 |
65 |
66 |
67 |
68 | org.apache.maven.plugins
69 | maven-compiler-plugin
70 | 3.1
71 |
72 | 1.7
73 | 1.7
74 |
75 |
76 |
77 |
78 |
79 |
80 | https://github.com/moxious/neoprofiler
81 |
82 | https://github.com/moxious/neoprofiler
83 |
84 | https://github.com/moxious/neoprofiler/issues
85 |
86 |
87 | 3.3.0
88 |
89 |
90 |
--------------------------------------------------------------------------------
/sample-report.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Profile of /home/x/provenance.db/ generated 2014/05/12 09:16:58
5 |
6 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
194 |
195 |
196 |
197 |
198 | # Profile of /home/x/provenance.db/ generated 2014/05/12 09:16:58
199 | /home/x/provenance.db/
200 |
201 | ## Schema
202 | *Information about Neo4J's database schema*
203 |
204 | ### Indexes
205 | * Constraint On nodes labeled Provenance. Property keys:
206 | * oid
207 | * Constraint On nodes labeled Actor. Property keys:
208 | * aid
209 | * Constraint On nodes labeled PrivilegeClass. Property keys:
210 | * pid
211 | * Constraint On nodes labeled NonProvenance. Property keys:
212 | * npid
213 |
214 | ### Non-Index Constraints
215 | * Constraint Type UNIQUENESS On nodes labeled Provenance. Property keys:
216 | * oid
217 | * Constraint Type UNIQUENESS On nodes labeled Actor. Property keys:
218 | * aid
219 | * Constraint Type UNIQUENESS On nodes labeled PrivilegeClass. Property keys:
220 | * pid
221 | * Constraint Type UNIQUENESS On nodes labeled NonProvenance. Property keys:
222 | * npid
223 |
224 | ### Observations
225 | * Run Time (ms): 3
226 |
227 |
228 | ## Label '(Unlabeled)'
229 | *Profile of nodes labeled '(Unlabeled)'*
230 |
231 | ### Observations
232 | * Outbound relationship types:
233 | * N/A
234 | * Run Time (ms): 2340
235 | * Sample properties:
236 | * Total: 0
237 |
238 |
239 | ## Nodes
240 | *Summary statistics about nodes in the graph*
241 |
242 | ### Observations
243 | * Node Labels:
244 | * [Provenance]
245 | * [Actor]
246 | * [PrivilegeClass]
247 | * Run Time (ms): 239
248 | * Total: 13266
249 |
250 |
251 | ## Relationships
252 | *Summary statistics about relationships in the graph*
253 |
254 | ### Observations
255 | * Available Relationship Types:
256 | * dominates
257 | * owns
258 | * triggered
259 | * controlledBy
260 | * contributed
261 | * generated
262 | * input to
263 | * Run Time (ms): 268
264 | * Total Relationships: 11948
265 |
266 |
267 | ## Label 'Provenance'
268 | *Profile of nodes labeled 'Provenance'*
269 |
270 | ### Observations
271 | * Outbound relationship types:
272 | * triggered
273 | * contributed
274 | * generated
275 | * input to
276 | * owns
277 | * Run Time (ms): 441
278 | * Sample properties:
279 | * oid (String) required
280 | * when_start (String) optional
281 | * workflow (String) optional
282 | * name (String) required
283 | * SGFs (String[]) optional
284 | * subtype (String) required
285 | * ownerid (String) required
286 | * when_end (String) optional
287 | * certainty (String) required
288 | * activity (String) optional
289 | * type (String) required
290 | * content (String) optional
291 | * created (Long) required
292 | * metadata:implus:motif (String) optional
293 | * Total nodes: 13225
294 |
295 |
296 | ## Label 'Actor'
297 | *Profile of nodes labeled 'Actor'*
298 |
299 | ### Observations
300 | * Outbound relationship types:
301 | * N/A
302 | * Run Time (ms): 44
303 | * Sample properties:
304 | * created (Long) optional
305 | * aid (String) optional
306 | * name (String) optional
307 | * type (String) optional
308 | * displayName (String) optional
309 | * Total nodes: 25
310 |
311 |
312 | ## Label 'PrivilegeClass'
313 | *Profile of nodes labeled 'PrivilegeClass'*
314 |
315 | ### Observations
316 | * Outbound relationship types:
317 | * dominates
318 | * controlledBy
319 | * Run Time (ms): 41
320 | * Sample properties:
321 | * name (String) optional
322 | * type (String) optional
323 | * pid (String) optional
324 | * created (Long) optional
325 | * description (String) optional
326 | * Total nodes: 16
327 |
328 |
329 | ## Relationships Typed 'dominates'
330 | *Profile of relationships of type 'dominates'*
331 |
332 | ### Observations
333 | * Run Time (ms): 190
334 | * Sample properties:
335 | * Total relationships: 16
336 | * domain:
337 | * PrivilegeClass
338 | * range:
339 | * PrivilegeClass
340 |
341 |
342 | ## Relationships Typed 'owns'
343 | *Profile of relationships of type 'owns'*
344 |
345 | ### Observations
346 | * Run Time (ms): 132
347 | * Sample properties:
348 | * Total relationships: 77
349 | * domain:
350 | * Actor
351 | * range:
352 | * Provenance
353 |
354 |
355 | ## Relationships Typed 'triggered'
356 | *Profile of relationships of type 'triggered'*
357 |
358 | ### Observations
359 | * Run Time (ms): 108
360 | * Sample properties:
361 | * workflow (String) required
362 | * Total relationships: 1257
363 | * domain:
364 | * Provenance
365 | * range:
366 | * Provenance
367 |
368 |
369 | ## Relationships Typed 'controlledBy'
370 | *Profile of relationships of type 'controlledBy'*
371 |
372 | ### Observations
373 | * Run Time (ms): 80
374 | * Sample properties:
375 | * Total relationships: 178
376 | * domain:
377 | * Provenance
378 | * range:
379 | * PrivilegeClass
380 |
381 |
382 | ## Relationships Typed 'contributed'
383 | *Profile of relationships of type 'contributed'*
384 |
385 | ### Observations
386 | * Run Time (ms): 93
387 | * Sample properties:
388 | * workflow (String) required
389 | * Total relationships: 483
390 | * domain:
391 | * Provenance
392 | * range:
393 | * Provenance
394 |
395 |
396 | ## Relationships Typed 'generated'
397 | *Profile of relationships of type 'generated'*
398 |
399 | ### Observations
400 | * Run Time (ms): 114
401 | * Sample properties:
402 | * workflow (String) required
403 | * Total relationships: 2911
404 | * domain:
405 | * Provenance
406 | * range:
407 | * Provenance
408 |
409 |
410 | ## Relationships Typed 'input to'
411 | *Profile of relationships of type 'input to'*
412 |
413 | ### Observations
414 | * Run Time (ms): 139
415 | * Sample properties:
416 | * workflow (String) required
417 | * Total relationships: 7026
418 | * domain:
419 | * Provenance
420 | * range:
421 | * Provenance
422 |
423 |
424 |
425 |
426 |
427 |
428 |
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/NeoProfiler.java:
--------------------------------------------------------------------------------
1 | package org.mitre.neoprofiler;
2 |
3 | import java.io.File;
4 | import java.io.FileWriter;
5 | import java.io.IOException;
6 | import java.io.StringWriter;
7 | import java.io.Writer;
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | import org.apache.commons.cli.CommandLine;
12 | import org.apache.commons.cli.CommandLineParser;
13 | import org.apache.commons.cli.GnuParser;
14 | import org.apache.commons.cli.HelpFormatter;
15 | import org.apache.commons.cli.OptionBuilder;
16 | import org.apache.commons.cli.Options;
17 | import org.apache.commons.cli.ParseException;
18 | import org.mitre.neoprofiler.html.HTMLMaker;
19 | import org.mitre.neoprofiler.markdown.MarkdownMaker;
20 | import org.mitre.neoprofiler.profile.DBProfile;
21 | import org.mitre.neoprofiler.profile.NeoProfile;
22 | import org.mitre.neoprofiler.profiler.NodesProfiler;
23 | import org.mitre.neoprofiler.profiler.Profiler;
24 | import org.mitre.neoprofiler.profiler.RelationshipsProfiler;
25 | import org.mitre.neoprofiler.profiler.SchemaProfiler;
26 | import org.mitre.neoprofiler.profiler.UnlabeledNodeProfiler;
27 | import org.neo4j.graphdb.GraphDatabaseService;
28 | import org.neo4j.graphdb.factory.GraphDatabaseFactory;
29 |
30 | import org.neo4j.driver.v1.AuthTokens;
31 | import org.neo4j.driver.v1.Driver;
32 | import org.neo4j.driver.v1.GraphDatabase;
33 | import org.neo4j.driver.v1.Session;
34 | import org.neo4j.driver.v1.StatementResult;
35 | import org.neo4j.driver.v1.Transaction;
36 | import org.neo4j.driver.v1.TransactionWork;
37 |
38 | import com.google.gson.Gson;
39 | import com.google.gson.GsonBuilder;
40 |
41 | public class NeoProfiler {
42 | protected Driver driver = null;
43 | protected String storageLoc = null;
44 | protected Session session = null;
45 | protected List schedule = new ArrayList();
46 |
47 | public enum Format { MARKDOWN, HTML, JSON };
48 |
49 | public NeoProfiler(String storageLoc, Driver driver) {
50 | this.driver = driver;
51 | this.storageLoc = storageLoc;
52 | }
53 |
54 | public Driver getDriver() { return driver; }
55 | public Transaction beginTx() {
56 | if (this.session == null) {
57 | this.session = driver.session();
58 | }
59 |
60 | return this.session.beginTransaction();
61 | }
62 |
63 | public void schedule(Profiler p) {
64 | schedule.add(p);
65 | }
66 |
67 | public DBProfile run() {
68 | DBProfile p = new DBProfile(storageLoc);
69 |
70 | schedule(new SchemaProfiler());
71 | schedule(new UnlabeledNodeProfiler());
72 | schedule(new NodesProfiler());
73 | schedule(new RelationshipsProfiler());
74 |
75 | int x=0;
76 |
77 | while(x < schedule.size()) {
78 | Profiler profiler = schedule.get(x);
79 | System.out.println("Running " + profiler.describe() + " (" + (x+1) + " of " + schedule.size() + ")");
80 | long t1 = System.currentTimeMillis();
81 |
82 | NeoProfile prof = null;
83 |
84 | try {
85 | prof = profiler.run(this);
86 | } catch(Exception exc) {
87 | System.err.println(profiler.getClass().getName() + " failed: " + exc.getMessage());
88 | exc.printStackTrace();
89 | x++;
90 | continue;
91 | }
92 | long t2 = System.currentTimeMillis();
93 |
94 | prof.addObservation("Run Time (ms)", (t2 - t1));
95 |
96 | if(prof != null) p.addProfile(prof);
97 | x++;
98 | }
99 |
100 | return p;
101 | } // End run
102 |
103 | public static Options makeCLIOptions() {
104 | Options options = new Options();
105 |
106 | options.addOption(OptionBuilder.withArgName("db")
107 | .hasArg()
108 | .isRequired()
109 | .withDescription("Path to directory where neo4j database is located")
110 | .create("db"));
111 |
112 | options.addOption(OptionBuilder.withArgName("format")
113 | .hasArg()
114 | .isRequired()
115 | .withDescription("Output format: valid values are json, markdown, or html")
116 | .create("format"));
117 |
118 | options.addOption(OptionBuilder.withArgName("output")
119 | .hasArg()
120 | .isRequired(false)
121 | .withDescription("Name of output file to write; program will print to console if not specified.")
122 | .create("output"));
123 |
124 | return options;
125 | }
126 |
127 | public static void main(String [] args) throws Exception {
128 | CommandLineParser parser = new GnuParser();
129 |
130 | try {
131 | CommandLine line = parser.parse(makeCLIOptions(), args );
132 |
133 | String path = line.getOptionValue("db");
134 | String format = line.getOptionValue("format");
135 | String output = line.getOptionValue("output");
136 |
137 | Writer destination = null;
138 | if(output == null) destination = new StringWriter();
139 |
140 | File f = new File(path);
141 |
142 | Format fmt = Format.HTML;
143 |
144 | if(format == null) fmt = Format.HTML;
145 | else if("html".equals(format)) fmt = Format.HTML;
146 | else if("json".equals(format)) fmt = Format.JSON;
147 | else if("markdown".equals(format)) fmt = Format.MARKDOWN;
148 | else {
149 | System.err.println("Invalid or unrecognized format '" + format + "': using default of html");
150 | }
151 |
152 | if(output != null) {
153 | try { destination = new FileWriter(output); }
154 | catch(IOException exc) {
155 | System.err.println("Can not create output file at " + output + ": " + exc.getMessage());
156 | usage();
157 | System.exit(1);
158 | }
159 | } else {
160 | // Instead of writing to a file, accumulate in this buffer then print out later.
161 | destination = new StringWriter();
162 | }
163 |
164 | // Run the profile.
165 | profile(path, fmt, destination);
166 |
167 | // Check for if final results are written to console, or out to a file.
168 | if(output == null) System.out.println(((StringWriter)destination).getBuffer());
169 | else destination.close();
170 |
171 | System.out.println("Done.");
172 | System.exit(0);
173 | } catch(ParseException exc) {
174 | System.err.println(exc.getMessage());
175 | usage();
176 | System.exit(1);
177 | }
178 | }
179 |
180 | public static void usage() {
181 | HelpFormatter formatter = new HelpFormatter();
182 | formatter.printHelp("NeoProfiler", makeCLIOptions());
183 | }
184 |
185 | public static void profile(String path, Format fmt, Writer output) throws IOException {
186 | String uri = "bolt://localhost";
187 | String user = "neo4j";
188 | String password = "admin";
189 |
190 | Driver driver = GraphDatabase.driver(uri, AuthTokens.basic( user, password ) );
191 |
192 | NeoProfiler profiler = new NeoProfiler(path, driver);
193 |
194 | DBProfile profile = profiler.run();
195 | MarkdownMaker mm = new MarkdownMaker();
196 |
197 | System.out.println("Writing report...");
198 | switch(fmt) {
199 | case JSON:
200 | Gson gson = new GsonBuilder().setPrettyPrinting().create();
201 | output.write(gson.toJson(profile));
202 | break;
203 |
204 | case MARKDOWN:
205 | mm.markdown(profile, output);
206 | break;
207 |
208 | case HTML:
209 | new HTMLMaker().html(profile, output);
210 | break;
211 |
212 | default:
213 | throw new RuntimeException("Invalid format");
214 | }
215 | }
216 | } // End NeoProfiler
217 |
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/html/HTMLMaker.java:
--------------------------------------------------------------------------------
1 | package org.mitre.neoprofiler.html;
2 |
3 | import java.io.IOException;
4 | import java.io.StringWriter;
5 | import java.io.Writer;
6 | import java.util.Collection;
7 | import java.util.List;
8 |
9 | import org.apache.velocity.Template;
10 | import org.apache.velocity.VelocityContext;
11 | import org.apache.velocity.app.VelocityEngine;
12 | import org.apache.velocity.runtime.RuntimeConstants;
13 | import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
14 | import org.mitre.neoprofiler.markdown.MarkdownMaker;
15 | import org.mitre.neoprofiler.profile.DBProfile;
16 | import org.mitre.neoprofiler.profile.LabelProfile;
17 | import org.mitre.neoprofiler.profile.NeoProfile;
18 | import org.mitre.neoprofiler.profile.RelationshipTypeProfile;
19 |
20 | /**
21 | * Class that renders a DBProfile as HTML.
22 | * The general approach is to write a graph structure in JSON rendered via D3.js, and to populate the rest
23 | * of the page with regular markdown from the MarkdownGenerator, rendered to HTML by strapdown.js.
24 | * @author moxious
25 | */
26 | public class HTMLMaker {
27 | public HTMLMaker() { ; }
28 |
29 | public String generateGraph(DBProfile profile) {
30 | StringBuffer b = new StringBuffer("var links = [\n");
31 |
32 | for(NeoProfile p : profile.getProfiles()) {
33 | if(p instanceof LabelProfile) {
34 | LabelProfile lp = (LabelProfile)p;
35 | String label = (String)lp.getParameter("label");
36 |
37 | List inbound = (List)lp.getObservations().get(LabelProfile.OB_INBOUND_RELATIONSHIP_TYPES);
38 | List outbound = (List)lp.getObservations().get(LabelProfile.OB_OUTBOUND_RELATIONSHIP_TYPES);
39 |
40 | for(Object o : outbound) {
41 | if((""+outbound).contains(NeoProfile.OB_VALUE_NA)) continue;
42 | b.append("{source: '[" + label + "]', target: 'REL: " + o + "', head: 'node', tail: 'relationship', type: 'outbound'},\n");
43 | }
44 |
45 | for(Object o : inbound) {
46 | if((""+inbound).contains(NeoProfile.OB_VALUE_NA)) continue;
47 | b.append("{source: 'REL: " + o + "', target: '[" + label + "]', head: 'relationship', tail: 'node', type: 'inbound'},\n");
48 | }
49 | } else if(p instanceof RelationshipTypeProfile) {
50 | RelationshipTypeProfile rt = (RelationshipTypeProfile)p;
51 | String type = (String)rt.getParameter("type");
52 |
53 | Collection domain = (Collection)rt.getObservations().get(RelationshipTypeProfile.OB_DOMAIN);
54 | Collection range = (Collection)rt.getObservations().get(RelationshipTypeProfile.OB_RANGE);
55 |
56 | for(String d : domain) {
57 | if(NeoProfile.OB_VALUE_NA.equals(""+d)) continue;
58 | b.append("{source: 'REL: " + type + "', target: '[" + d + "]', head: 'relationship', tail: 'node', type: 'domain'},\n");
59 | }
60 |
61 | for(String r : range) {
62 | if(NeoProfile.OB_VALUE_NA.equals(""+r)) continue;
63 | b.append("{source: '[" + r + "]', target: 'REL: " + type + "', head: 'node', tail: 'relationship', type: 'range'},\n");
64 | }
65 | }
66 | }
67 |
68 | b.append("];\n");
69 | return b.toString();
70 | }
71 |
72 | public void html(DBProfile profile, Writer writer) throws IOException {
73 | VelocityEngine ve = new VelocityEngine();
74 |
75 | ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
76 | ve.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
77 |
78 | ve.init();
79 |
80 | Template t = null;
81 |
82 | if(ve.resourceExists("/template.html"))
83 | t = ve.getTemplate("/template.html");
84 | else {
85 | try {
86 | t = ve.getTemplate("src/main/resources/template.html");
87 | } catch(Exception exc) {
88 | System.err.println("The application could not find a needed HTML template.");
89 | System.err.println("This is an unusual problem; please report it as an issue, and provide details of your configuration.");
90 | throw new RuntimeException("Could not find HTML template as resource.");
91 | }
92 | }
93 |
94 | VelocityContext context = new VelocityContext();
95 |
96 | StringWriter markdownContent = new StringWriter();
97 |
98 | // Write markdown content....
99 | new MarkdownMaker().markdown(profile, markdownContent);
100 |
101 | context.put("title", profile.getName());
102 | context.put("markdown", markdownContent.getBuffer().toString());
103 | context.put("links", generateGraph(profile));
104 | //context.put("d3graph", d3graph.toString());
105 |
106 | // Dump the contents of the template merged with the context. That's it!
107 | t.merge(context, writer);
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/html/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | /**
5 | * @author x
6 | *
7 | */
8 | package org.mitre.neoprofiler.html;
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/markdown/MarkdownMaker.java:
--------------------------------------------------------------------------------
1 | package org.mitre.neoprofiler.markdown;
2 |
3 | import java.io.IOException;
4 | import java.io.Writer;
5 | import java.util.ArrayList;
6 | import java.util.Collection;
7 | import java.util.Collections;
8 | import java.util.List;
9 | import java.util.Map;
10 |
11 | import org.mitre.neoprofiler.profile.DBProfile;
12 | import org.mitre.neoprofiler.profile.NeoConstraint;
13 | import org.mitre.neoprofiler.profile.NeoProfile;
14 | import org.mitre.neoprofiler.profile.ParameterizedNeoProfile;
15 | import org.mitre.neoprofiler.profile.SchemaProfile;
16 |
17 | /**
18 | * A class to write profiles as Markdown.
19 | * @author moxious
20 | */
21 | public class MarkdownMaker {
22 | public MarkdownMaker() {
23 |
24 | }
25 |
26 | public String link(String text, String url) { return "[" + text + "](" + url + ")"; }
27 | public String h1(String content) { return "\n# " + content + "\n"; }
28 | public String h2(String content) { return "\n## " + content + "\n"; }
29 | public String h3(String content) { return "\n### " + content + "\n"; }
30 | public String h4(String content) { return "\n#### " + content + "\n"; }
31 |
32 | protected String observations(Mapobservations) {
33 | if(observations.isEmpty()) return "";
34 |
35 | StringBuffer b = new StringBuffer("");
36 |
37 | b.append(h3("Observations"));
38 |
39 | ArrayListkeys = new ArrayList(observations.keySet());
40 | Collections.sort(keys);
41 |
42 | for(String key : keys) {
43 | b.append("* " + key + ": " + observation(observations.get(key)));
44 |
45 | if(b.charAt(b.length() - 1) != '\n') b.append("\n");
46 | }
47 |
48 | return b.toString();
49 | }
50 |
51 | protected String observation(Object ob) {
52 | if(ob == null) return "N/A";
53 | if(ob instanceof String || ob instanceof Number) return ""+ob;
54 | if(ob instanceof Collection) {
55 | StringBuffer b = new StringBuffer("\n");
56 |
57 | for(Object o : ((Collection)ob)) {
58 | b.append(" * " + o + "\n");
59 | }
60 |
61 | return b.toString();
62 | }
63 |
64 | return ""+ob;
65 | } // End observation
66 |
67 | public String constraints(String title, List constraints) {
68 | StringBuffer b = new StringBuffer("");
69 | b.append(h3(title));
70 |
71 | if(constraints.isEmpty()) b.append("**None found**");
72 | else {
73 | for(NeoConstraint con : constraints) {
74 | b.append("* " + (con.isIndex() ? "Index" : "Constraint") + " " + con.getDescription() + "\n");
75 | }
76 | }
77 |
78 | return b.toString();
79 | }
80 |
81 | public String parameters(Mapparameters) {
82 | if(parameters.isEmpty()) return "";
83 |
84 | StringBuffer b = new StringBuffer("");
85 |
86 | b.append(h3("Parameters"));
87 |
88 | ArrayListkeys = new ArrayList(parameters.keySet());
89 | Collections.sort(keys);
90 |
91 | for(String key : keys) {
92 | b.append("* " + key + ": " + observation(parameters.get(key)) + "\n");
93 | }
94 |
95 | b.append("\n");
96 |
97 | return b.toString();
98 | }
99 |
100 | public void markdownComponentProfile(NeoProfile profile, Writer writer) throws IOException {
101 | writer.write(h2(profile.getName()) + "*" + profile.getDescription() + "*\n");
102 |
103 | //if(profile instanceof ParameterizedNeoProfile) {
104 | // writer.write(parameters(((ParameterizedNeoProfile)profile).getParameters()));
105 | //}
106 |
107 | if(profile instanceof SchemaProfile) {
108 | SchemaProfile sp = (SchemaProfile)profile;
109 | writer.write(constraints("Indexes", sp.getIndexes()));
110 | writer.write(constraints("Non-Index Constraints", sp.getNonIndexes()));
111 | }
112 |
113 | writer.write(observations(profile.getObservations()));
114 | writer.write("\n");
115 | }
116 |
117 | public void markdown(DBProfile profile, Writer writer) throws IOException {
118 | writer.write(h1(profile.getName()) +
119 | profile.getDescription() + "\n\n");
120 |
121 | writer.write("Generated by [NeoProfiler](https://github.com/moxious/neoprofiler)\n\n");
122 |
123 | writer.write(observations(profile.getObservations()));
124 |
125 | for(NeoProfile p : profile.getProfiles()) {
126 | markdownComponentProfile(p, writer);
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/markdown/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | /**
5 | * @author moxious
6 | *
7 | */
8 | package org.mitre.neoprofiler.markdown;
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | /**
5 | * @author moxious
6 | */
7 | package org.mitre.neoprofiler;
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/profile/DBProfile.java:
--------------------------------------------------------------------------------
1 | package org.mitre.neoprofiler.profile;
2 |
3 | import java.text.DateFormat;
4 | import java.text.SimpleDateFormat;
5 | import java.util.ArrayList;
6 | import java.util.Date;
7 | import java.util.List;
8 |
9 | public class DBProfile extends NeoProfile {
10 | protected List profiles = new ArrayList();
11 |
12 | public DBProfile(String storageLoc) {
13 | DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
14 | Date date = new Date();
15 |
16 | name = "Profile of " + storageLoc + " generated " + dateFormat.format(date);
17 | description = storageLoc;
18 | }
19 |
20 | public List getProfiles() { return profiles; }
21 |
22 | public List getLabels() {
23 | ArrayList arr = new ArrayList();
24 |
25 | for(NeoProfile p : getProfiles())
26 | if(p instanceof LabelProfile)
27 | arr.add((LabelProfile)p);
28 |
29 | return arr;
30 | }
31 |
32 | public List getRelationshipTypes() {
33 | ArrayList arr = new ArrayList();
34 |
35 | for(NeoProfile p : getProfiles())
36 | if(p instanceof RelationshipTypeProfile)
37 | arr.add((RelationshipTypeProfile)p);
38 |
39 | return arr;
40 | }
41 |
42 | public RelationshipTypeProfile getRelationshipTypeProfile(String type) {
43 | for(NeoProfile p : getProfiles()) {
44 | if(!(p instanceof RelationshipTypeProfile)) continue;
45 |
46 | if(type.equals(((RelationshipTypeProfile)p).getParameter("type")))
47 | return ((RelationshipTypeProfile)p);
48 | }
49 |
50 | return null;
51 | }
52 |
53 | public LabelProfile getLabelProfile(String label) {
54 | for(NeoProfile p : getProfiles()) {
55 | if(!(p instanceof LabelProfile)) continue;
56 |
57 | if(label.equals(((LabelProfile)p).getParameter("label")))
58 | return ((LabelProfile)p);
59 | }
60 |
61 | return null;
62 | }
63 |
64 | public void addProfile(NeoProfile profile) {
65 | profiles.add(profile);
66 | }
67 |
68 | public String toString() {
69 | StringBuffer b = new StringBuffer("");
70 |
71 | b.append("DBProfile " + name + "\n");
72 | b.append("DBProfile " + description + "\n\n");
73 |
74 | for(NeoProfile profile : profiles) {
75 | b.append(profile.toString() + "\n");
76 | }
77 |
78 | return b.toString();
79 | }
80 | } // End DBProfile
81 |
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/profile/LabelProfile.java:
--------------------------------------------------------------------------------
1 | package org.mitre.neoprofiler.profile;
2 |
3 | public class LabelProfile extends ParameterizedNeoProfile {
4 | public static String OB_OUTBOUND_RELATIONSHIP_TYPES = "Outbound relationship types";
5 | public static String OB_INBOUND_RELATIONSHIP_TYPES = "Outbound relationship types";
6 |
7 | public LabelProfile(String label) {
8 | name="Label '" + label + "'";
9 | description="Profile of nodes labeled '" + label + "'";
10 | setParameter("label", label);
11 | setParameter("sampleSize", new Integer(100));
12 | }
13 | }
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/profile/LabelPropertyProfile.java:
--------------------------------------------------------------------------------
1 | package org.mitre.neoprofiler.profile;
2 |
3 | public class LabelPropertyProfile extends ParameterizedNeoProfile {
4 | public String label;
5 | public String property;
6 |
7 | public static final String OB_CARDINALITY = "Cardinality";
8 |
9 | public LabelPropertyProfile(String label, String property) {
10 | this.name = "Property '" + property + "' on '" + label + "' nodes";
11 | this.description = "Profile of the values of properties named '" + property + "' on nodes labeled '" + label + "'";
12 | this.label = label;
13 | this.property = property;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/profile/NeoConstraint.java:
--------------------------------------------------------------------------------
1 | package org.mitre.neoprofiler.profile;
2 |
3 | public class NeoConstraint {
4 | protected String description;
5 | protected boolean index;
6 |
7 | public NeoConstraint(String description) {
8 | this(description, false);
9 | }
10 |
11 | public NeoConstraint(String description, boolean index) {
12 | this.description = description;
13 | this.index = index;
14 | }
15 |
16 | public String toString() {
17 | return "NeoConstraint constraint=" + description;
18 | }
19 |
20 | public boolean isIndex() { return index == true; }
21 | public String getDescription() { return description; }
22 | } // End NeoConstraint
23 |
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/profile/NeoProfile.java:
--------------------------------------------------------------------------------
1 | package org.mitre.neoprofiler.profile;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collections;
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | public abstract class NeoProfile {
9 | public static final String OB_COUNT = "Total";
10 | public static final String OB_SAMPLE_PROPERTIES = "Sample properties";
11 | public static final String OB_VALUE_NA = "N/A";
12 |
13 | protected String name;
14 | protected String description;
15 | protected HashMap observations = new HashMap();
16 |
17 | public String getName() { return name; }
18 | public String getDescription() { return description; }
19 | public Map getObservations() { return observations; }
20 | public void addObservation(String name, Object observation) { observations.put(name, observation); }
21 |
22 | public boolean has(String observationName) {
23 | return observations.containsKey(observationName);
24 | }
25 |
26 | public String toString() {
27 | StringBuffer b = new StringBuffer("");
28 | b.append(name + ": " + description + "\n");
29 |
30 | ArrayList keys = new ArrayList(getObservations().keySet());
31 | Collections.sort(keys);
32 |
33 | for(String key : keys) {
34 | b.append(key + ": " + observations.get(key) + "\n");
35 | }
36 |
37 | return b.toString();
38 | } // End toString
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/profile/NeoProperty.java:
--------------------------------------------------------------------------------
1 | package org.mitre.neoprofiler.profile;
2 |
3 | /**
4 | * Small model container class for a property on a node, along with a type inferred for that property based on sample data.
5 | * @author moxious
6 | */
7 | public class NeoProperty {
8 | protected String name;
9 | protected String type;
10 | protected boolean optional = true;
11 |
12 | public NeoProperty(String name, String type) {
13 | if(name == null) name = "";
14 | if(type == null) type = "";
15 |
16 | this.name = name;
17 | this.type = type;
18 | }
19 |
20 | public void setOptional(boolean optional) { this.optional = optional; }
21 | public boolean getOptional() { return optional; }
22 | public String getName() { return name; }
23 | public String getType() { return type; }
24 |
25 | public boolean equals(Object other) {
26 | if(!(other instanceof NeoProperty)) return false;
27 |
28 | NeoProperty o = (NeoProperty)other;
29 |
30 | if(!name.equals(o.getName())) return false;
31 | if(!type.equals(o.getType())) return false;
32 |
33 | return true;
34 | }
35 |
36 | public String toString() {
37 | return name + " (" + type + ") " + (optional ? "optional" : "required");
38 | }
39 | } // End NeoProperty
40 |
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/profile/NodesProfile.java:
--------------------------------------------------------------------------------
1 | package org.mitre.neoprofiler.profile;
2 |
3 | public class NodesProfile extends NeoProfile {
4 | public NodesProfile() {
5 | name = "Nodes";
6 | description = "Summary statistics about nodes in the graph";
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/profile/ParameterizedNeoProfile.java:
--------------------------------------------------------------------------------
1 | package org.mitre.neoprofiler.profile;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | public class ParameterizedNeoProfile extends NeoProfile {
7 | protected Map params = new HashMap();
8 |
9 | public Object getParameter(String name) { return params.get(name); }
10 | public void setParameter(String name, Object param) { params.put(name, param); }
11 |
12 | public Map getParameters() { return params; }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/profile/RelationshipTypeProfile.java:
--------------------------------------------------------------------------------
1 | package org.mitre.neoprofiler.profile;
2 |
3 | public class RelationshipTypeProfile extends ParameterizedNeoProfile {
4 | public static final String OB_DOMAIN = "domain";
5 | public static final String OB_RANGE = "range";
6 |
7 | public RelationshipTypeProfile(String type) {
8 | name="Relationships Typed '" + type + "'";
9 | description="Profile of relationships of type '" + type + "'";
10 | setParameter("type", type);
11 | setParameter("sampleSize", new Integer(100));
12 | }
13 | }
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/profile/RelationshipsProfile.java:
--------------------------------------------------------------------------------
1 | package org.mitre.neoprofiler.profile;
2 |
3 | public class RelationshipsProfile extends NeoProfile {
4 | public RelationshipsProfile() {
5 | name = "Relationships";
6 | description = "Summary statistics about relationships in the graph";
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/profile/SchemaProfile.java:
--------------------------------------------------------------------------------
1 | package org.mitre.neoprofiler.profile;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | public class SchemaProfile extends NeoProfile {
7 | List constraints;
8 |
9 | public SchemaProfile() {
10 | name="Schema";
11 | description="Information about Neo4J's database schema";
12 |
13 | constraints = new ArrayList();
14 | }
15 |
16 | public void addConstraint(NeoConstraint constraint) {
17 | constraints.add(constraint);
18 | }
19 |
20 | public List getConstraints() {
21 | return constraints;
22 | }
23 |
24 | public List getNonIndexes() {
25 | List idxs = new ArrayList();
26 |
27 | for(NeoConstraint c : constraints) {
28 | if(!c.index) idxs.add(c);
29 | }
30 |
31 | return idxs;
32 | }
33 |
34 | public List getIndexes() {
35 | List idxs = new ArrayList();
36 |
37 | for(NeoConstraint c : constraints) {
38 | if(c.index) idxs.add(c);
39 | }
40 |
41 | return idxs;
42 | }
43 | } // End SchemaProfile
44 |
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/profile/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Package that defines model classes for various kinds of profiles that can be extracted from a database.
3 | */
4 | /**
5 | * @author moxious
6 | *
7 | */
8 | package org.mitre.neoprofiler.profile;
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/profiler/LabelProfiler.java:
--------------------------------------------------------------------------------
1 | package org.mitre.neoprofiler.profiler;
2 |
3 | import java.util.HashMap;
4 | import java.util.HashSet;
5 | import java.util.List;
6 | import java.util.logging.Logger;
7 |
8 | import org.mitre.neoprofiler.NeoProfiler;
9 | import org.mitre.neoprofiler.profile.LabelProfile;
10 | import org.mitre.neoprofiler.profile.NeoProfile;
11 | import org.mitre.neoprofiler.profile.NeoProperty;
12 | import org.neo4j.graphdb.Node;
13 | import org.neo4j.driver.v1.Transaction;
14 | import org.neo4j.driver.v1.Value;
15 |
16 | public class LabelProfiler extends QueryRunner implements Profiler {
17 | private static final Logger log = Logger.getLogger(LabelProfiler.class.getName());
18 | String label = null;
19 |
20 | public LabelProfiler(String label) {
21 | if(label == null || "".equals(label)) {
22 | log.severe("Invalid input label: '" + label + "'");
23 | }
24 |
25 | this.label = label.replaceAll("\\[", "").replaceAll("\\]", "");
26 |
27 | if (this.label.charAt(0) == '"' && this.label.charAt(this.label.length() - 1) == '"') {
28 | this.label = this.label.substring(1, this.label.length() - 1);
29 | }
30 |
31 | if("".equals(this.label)) {
32 | log.severe("Invalid processed label '" + label + "' => '" + this.label + "'");
33 | }
34 | }
35 |
36 | public NeoProfile run(NeoProfiler parent) {
37 | LabelProfile p = new LabelProfile(label);
38 |
39 | Object result = runQuerySingleResult(parent, "match (n:`" + label +"`) return count(n) as c", "c");
40 | p.addObservation("Total nodes", ""+result);
41 |
42 | int sampleSize = (Integer)p.getParameter("sampleSize");
43 | ListnodeSamples = runQueryMultipleResult(parent, "match (n:`" + label + "`) return n as instance limit " + sampleSize,
44 | "instance");
45 |
46 | try ( Transaction tx = parent.getDriver().session().beginTransaction() ) {
47 | HashSet props = new HashSet();
48 | HashMap seen = new HashMap();
49 |
50 | for(Object ns : nodeSamples) {
51 | for(NeoProperty prop : getSampleProperties(parent, ((Value)ns).asNode())) {
52 | String key = prop.toString();
53 |
54 | // Increment counter.
55 | if(!seen.containsKey(key)) { seen.put(key, 1); }
56 | else { seen.put(key, seen.get(key) + 1); continue; }
57 |
58 | props.add(prop);
59 | }
60 | }
61 |
62 | // If a property was seen in some (but not all) samples, then it's optional.
63 | for(NeoProperty prop : props) {
64 | int count = seen.get(prop.toString());
65 | System.out.println("For label " + label + " property " + prop + " seen " + count + " of " + sampleSize + " times.");
66 | if(count < sampleSize) prop.setOptional(true);
67 | else prop.setOptional(false);
68 |
69 | parent.schedule(new LabelPropertyProfiler(label, prop.getName()));
70 | }
71 |
72 | p.addObservation(NeoProfile.OB_SAMPLE_PROPERTIES, props);
73 | } // End try
74 |
75 | Listoutbound = runQueryMultipleResult(parent,
76 | "match (n:`" + label + "`)-[r]->(m) where n <> m return distinct(type(r)) as outbound", "outbound");
77 |
78 | if(outbound.isEmpty()) outbound.add(NeoProfile.OB_VALUE_NA);
79 | p.addObservation(LabelProfile.OB_OUTBOUND_RELATIONSHIP_TYPES, outbound);
80 |
81 | Listinbound = runQueryMultipleResult(parent,
82 | "match (n:`" + label + "`)<-[r]-(m) where n <> m return distinct(type(r)) as outbound", "outbound");
83 |
84 | if(inbound.isEmpty()) inbound.add(NeoProfile.OB_VALUE_NA);
85 | p.addObservation(LabelProfile.OB_INBOUND_RELATIONSHIP_TYPES, inbound);
86 |
87 | // TODO Auto-generated method stub
88 | return p;
89 | }
90 |
91 | public String describe() {
92 | return "LabelProfiler(" + label + ")";
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/profiler/LabelPropertyProfiler.java:
--------------------------------------------------------------------------------
1 | package org.mitre.neoprofiler.profiler;
2 |
3 | import org.mitre.neoprofiler.NeoProfiler;
4 | import org.mitre.neoprofiler.profile.LabelPropertyProfile;
5 | import org.mitre.neoprofiler.profile.NeoProfile;
6 |
7 |
8 | public class LabelPropertyProfiler extends QueryRunner implements Profiler {
9 | protected String label;
10 | protected String property;
11 |
12 | public LabelPropertyProfiler(String label, String property) {
13 | this.label = label;
14 | this.property = property;
15 | }
16 |
17 | public NeoProfile run(NeoProfiler parent) {
18 | LabelPropertyProfile prof = new LabelPropertyProfile(label, property);
19 |
20 | // Determine how many unique observations
21 | prof.addObservation(LabelPropertyProfile.OB_CARDINALITY,
22 | runQuerySingleResult(parent, "match (n:`" + label + "`) return count(distinct(n.`" + property +"`)) as n", "n"));
23 |
24 | return prof;
25 | }
26 |
27 | public String describe() {
28 | return "LabelPropertyProfiler - (:" + label + " { " + property + " })";
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/profiler/NodesProfiler.java:
--------------------------------------------------------------------------------
1 | package org.mitre.neoprofiler.profiler;
2 |
3 | import java.util.List;
4 |
5 | import org.apache.commons.lang.StringUtils;
6 | import org.mitre.neoprofiler.NeoProfiler;
7 | import org.mitre.neoprofiler.profile.NeoProfile;
8 | import org.mitre.neoprofiler.profile.NodesProfile;
9 |
10 | /**
11 | * Profiling queries to be run on all nodes. This has the effect of spawning additional profilers as needed.
12 | * @author moxious
13 | */
14 | public class NodesProfiler extends QueryRunner implements Profiler {
15 | public NeoProfile run(NeoProfiler parent) {
16 | NodesProfile p = new NodesProfile();
17 |
18 | List labels = runQueryMultipleResult(parent, "match (n) return distinct(labels(n)) as labels", "labels");
19 |
20 | for(Object l : labels) {
21 | String labelTxt;
22 |
23 | // Schedule a new profiler to look into the particular label we just discovered.
24 | if (l instanceof List) {
25 | List labelNames = (List) l;
26 | labelTxt = StringUtils.join(labelNames, ":");
27 | } else {
28 | labelTxt = ""+l;
29 | }
30 |
31 |
32 | // If the database has no labels, then querying for them returns the empty set [].
33 | // Don't try to schedule inspection of [] as a label, because that will bomb.
34 | if(labelTxt != null && !"".equals(labelTxt) && !"[]".equals(labelTxt))
35 | parent.schedule(new LabelProfiler(labelTxt));
36 | }
37 |
38 | p.addObservation("Node Labels", labels);
39 | p.addObservation(NeoProfile.OB_COUNT, runQuerySingleResult(parent, "match (n) return count(n) as c", "c"));
40 |
41 | return p;
42 | } // End run
43 |
44 | public String describe() {
45 | return "NodesProfiler";
46 | }
47 | } // End NodesProfiler
48 |
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/profiler/PathsProfiler.java:
--------------------------------------------------------------------------------
1 | package org.mitre.neoprofiler.profiler;
2 |
3 | import org.mitre.neoprofiler.NeoProfiler;
4 | import org.mitre.neoprofiler.profile.NeoProfile;
5 |
6 | /**
7 | * Profiler for paths through the database; currently doesn't do much other than act as a placeholder.
8 | * @author moxious
9 | * TODO
10 | */
11 | public class PathsProfiler extends QueryRunner implements Profiler {
12 | public NeoProfile run(NeoProfiler parent) {
13 | PathsProfile p = new PathsProfile();
14 | return p;
15 | }
16 |
17 | public class PathsProfile extends NeoProfile {
18 | public PathsProfile() {
19 | name = "PathsProfile";
20 | description = "Profile of various paths of interest through this graph.";
21 | }
22 | }
23 |
24 | public String describe() {
25 | return "PathsProfiler";
26 | }
27 | } // End PathsProfiler
28 |
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/profiler/Profiler.java:
--------------------------------------------------------------------------------
1 | package org.mitre.neoprofiler.profiler;
2 |
3 | import org.mitre.neoprofiler.NeoProfiler;
4 | import org.mitre.neoprofiler.profile.NeoProfile;
5 |
6 | public interface Profiler {
7 | public NeoProfile run(NeoProfiler parent);
8 | public String describe();
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/profiler/QueryRunner.java:
--------------------------------------------------------------------------------
1 | package org.mitre.neoprofiler.profiler;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashMap;
5 | import java.util.Iterator;
6 | import java.util.List;
7 | import java.util.Map;
8 | import java.util.logging.Logger;
9 |
10 | import org.mitre.neoprofiler.NeoProfiler;
11 | import org.mitre.neoprofiler.profile.NeoProperty;
12 | import org.neo4j.graphdb.PropertyContainer;
13 | import org.neo4j.graphdb.ResourceIterator;
14 |
15 | import org.neo4j.driver.v1.AuthTokens;
16 | import org.neo4j.driver.v1.Value;
17 | import org.neo4j.driver.v1.Driver;
18 | import org.neo4j.driver.v1.GraphDatabase;
19 | import org.neo4j.driver.v1.Session;
20 | import org.neo4j.driver.v1.StatementResult;
21 | import org.neo4j.driver.v1.Transaction;
22 | import org.neo4j.driver.v1.TransactionWork;
23 | import org.neo4j.driver.v1.types.Entity;
24 |
25 | /**
26 | * Abstract class that contains various query running utilities to make implementation of downstream profilers easier.
27 | * @author moxious
28 | */
29 | public abstract class QueryRunner {
30 | protected static final Logger log = Logger.getLogger(QueryRunner.class.getName());
31 |
32 | public List getSampleProperties(NeoProfiler parent, Entity n) {
33 | List props = new ArrayList();
34 |
35 | Session s = parent.getDriver().session();
36 | try ( Transaction tx = s.beginTransaction() ) {
37 | Iterator propKeys = n.keys().iterator();
38 |
39 | while(propKeys.hasNext()) {
40 | String key = propKeys.next();
41 | Object val = n.get(key);
42 |
43 | props.add(new NeoProperty(key, val.getClass().getSimpleName()));
44 | }
45 |
46 | } finally {
47 | s.close();
48 | }
49 |
50 | return props;
51 | }
52 |
53 | public Map> runQueryComplexResult(NeoProfiler parent, String query, String...columns) {
54 | HashMap> all = new HashMap>();
55 |
56 | List retvals = new ArrayList();
57 | System.out.println(query);
58 | Session s = parent.getDriver().session();
59 | try ( Transaction tx = s.beginTransaction()) {
60 | // log.info(query);
61 | StatementResult result = tx.run(query);
62 |
63 | while(result.hasNext()) {
64 | Map row = result.next().asMap();
65 |
66 | for(String col : columns) {
67 | if(!all.containsKey(col)) all.put(col, new ArrayList());
68 | all.get(col).add(row.get(col));
69 | }
70 | }
71 |
72 | tx.close();
73 | } finally {
74 | s.close();
75 | }
76 |
77 | return all;
78 | }
79 |
80 | public Value runQuerySingleResult(NeoProfiler parent, String query, String columnReturn) {
81 | Session s = parent.getDriver().session();
82 |
83 | try ( Transaction tx = s.beginTransaction()) {
84 | // log.info(query);
85 | StatementResult result = tx.run(query);
86 |
87 | if(result.hasNext()) {
88 | Value val = result.next().get(columnReturn);
89 | return val;
90 | }
91 |
92 | return null;
93 | } finally {
94 | s.close();
95 | }
96 | }
97 |
98 | public List runQueryMultipleResult(NeoProfiler parent, String query, String columnReturn) {
99 | Session s = parent.getDriver().session();
100 | List retvals = new ArrayList();
101 |
102 | try ( Transaction tx = s.beginTransaction()) {
103 | // log.info(query);
104 | StatementResult result = tx.run(query);
105 |
106 | while(result.hasNext()) {
107 | retvals.add(result.next().get(columnReturn));
108 | }
109 | }
110 |
111 | return retvals;
112 | }
113 |
114 | /*
115 | public String stringList(Iterable> objs) {
116 | StringBuffer b = new StringBuffer("");
117 | Iterator> it = objs.iterator();
118 | while(it.hasNext()) b.append(""+it.next() + " ");
119 | return b.toString();
120 | }
121 |
122 | public String stringList(List objs) {
123 | StringBuffer b = new StringBuffer("");
124 |
125 | for(int x=0; x(m) return count(r) as c", "c");
39 | p.addObservation("Total relationships", ""+result);
40 |
41 | Session s = parent.getDriver().session();
42 | StatementResult stmtResult = s.run(
43 | "match (n)-[r:`" + type + "`]->(m) return n as left, m as right, r as rel limit " + sampleSize);
44 |
45 | Map> ret = runQueryComplexResult(parent,
46 | "match (n)-[r:`" + type + "`]->(m) return n as left, m as right, r as rel limit " + sampleSize,
47 | "right", "left", "rel");
48 |
49 | try ( Transaction tx = parent.beginTx() ) {
50 | HashSet props = new HashSet();
51 | HashMap seen = new HashMap();
52 |
53 | while(stmtResult.hasNext()) {
54 | Value r = stmtResult.next().get("rel");
55 | for(NeoProperty prop : getSampleProperties(parent, r.asRelationship())) {
56 | String key = prop.toString();
57 |
58 | // Increment counter.
59 | if(!seen.containsKey(key)) { seen.put(key, 1); }
60 | else { seen.put(key, seen.get(key) + 1); continue; }
61 |
62 | props.add(prop);
63 | }
64 | }
65 |
66 | // If a property was seen in some (but not all) samples, then it's optional.
67 | for(NeoProperty prop : props) {
68 | int count = seen.get(prop.toString());
69 | System.out.println("For relationship " + type + " property " + prop + " seen " + count + " of " + sampleSize + " times.");
70 | if(count < sampleSize) prop.setOptional(true);
71 | else prop.setOptional(false);
72 | }
73 |
74 | p.addObservation(NeoProfile.OB_SAMPLE_PROPERTIES, props);
75 |
76 | HashSet labels = new HashSet();
77 |
78 | for(Object headNode : ret.get("left")) {
79 | Iterator headLabels = ((InternalNode)headNode).labels().iterator();
80 | while(headLabels.hasNext()) labels.add(headLabels.next());
81 | }
82 |
83 | p.addObservation(RelationshipTypeProfile.OB_DOMAIN, labels);
84 |
85 | labels = new HashSet();
86 | for(Object tailNode : ret.get("right")) {
87 | Iterator tailLabels = ((InternalNode)tailNode).labels().iterator();
88 | while(tailLabels.hasNext()) labels.add(tailLabels.next());
89 | }
90 |
91 | p.addObservation(RelationshipTypeProfile.OB_RANGE, labels);
92 | } // End try
93 |
94 | return p;
95 | }
96 |
97 | public String describe() {
98 | return "RelationshipTypeProfiler -[:" + type + "]->";
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/profiler/RelationshipsProfiler.java:
--------------------------------------------------------------------------------
1 | package org.mitre.neoprofiler.profiler;
2 |
3 | import java.util.List;
4 |
5 | import org.mitre.neoprofiler.NeoProfiler;
6 | import org.mitre.neoprofiler.profile.NeoProfile;
7 | import org.mitre.neoprofiler.profile.RelationshipsProfile;
8 |
9 | /**
10 | * Gathers basic summary statistics on relationships, and schedules additional
11 | * RelationshipTypeProfilers to run depending on what is found.
12 | * @author moxious
13 | */
14 | public class RelationshipsProfiler extends QueryRunner implements Profiler {
15 | public NeoProfile run(NeoProfiler parent) {
16 | RelationshipsProfile p = new RelationshipsProfile();
17 | List relTypes = runQueryMultipleResult(parent, "start r=relationship(*) return distinct(type(r)) as relTypes", "relTypes");
18 | p.addObservation("Available Relationship Types", relTypes);
19 |
20 | for(Object relType : relTypes) {
21 | String relTypeName = ""+relType;
22 |
23 | if (relTypeName.charAt(0) == '"' && relTypeName.charAt(relTypeName.length() - 1) == '"') {
24 | relTypeName = relTypeName.substring(1, relTypeName.length() - 1);
25 | }
26 |
27 | parent.schedule(new RelationshipTypeProfiler(relTypeName));
28 | }
29 |
30 | p.addObservation("Total Relationships", runQuerySingleResult(parent, "start r=relationship(*) return count(r) as c", "c"));
31 |
32 | return p;
33 | }
34 |
35 | public String describe() {
36 | return "RelationshipsProfiler";
37 | }
38 | } // End RelationshipsProfiler
39 |
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/profiler/SchemaProfiler.java:
--------------------------------------------------------------------------------
1 | package org.mitre.neoprofiler.profiler;
2 |
3 | import java.util.Iterator;
4 | import java.util.List;
5 | import java.util.Map;
6 |
7 | import org.mitre.neoprofiler.NeoProfiler;
8 | import org.mitre.neoprofiler.profile.NeoConstraint;
9 | import org.mitre.neoprofiler.profile.NeoProfile;
10 | import org.mitre.neoprofiler.profile.SchemaProfile;
11 | import org.neo4j.driver.v1.Session;
12 | import org.neo4j.driver.v1.Transaction;
13 | import org.neo4j.driver.v1.StatementResult;
14 | import org.neo4j.driver.v1.Record;
15 | import org.neo4j.graphdb.schema.ConstraintDefinition;
16 | import org.neo4j.graphdb.schema.IndexDefinition;
17 | import org.neo4j.graphdb.schema.Schema;
18 |
19 | /**
20 | * Profiler which pulls whatever information is possible out of the Schema object provided by Neo4J
21 | * @author moxious
22 | */
23 | public class SchemaProfiler extends QueryRunner implements Profiler {
24 | public NeoProfile run(NeoProfiler parent) {
25 | SchemaProfile p = new SchemaProfile();
26 |
27 | Map> indexes = runQueryComplexResult(parent, "call db.indexes();", "description", "state", "type");
28 | Map> constraints = runQueryComplexResult(parent, "call db.constraints();", "description");
29 |
30 | for(Object index : indexes.get("description")) {
31 | p.addConstraint(new NeoConstraint("" + index, true));
32 | }
33 |
34 | for(Object constraint : constraints.get("description")) {
35 | p.addConstraint(new NeoConstraint(""+constraint));
36 | }
37 |
38 | return p;
39 | }
40 |
41 | public String describe() {
42 | return "SchemaProfiler";
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/profiler/UnlabeledNodeProfiler.java:
--------------------------------------------------------------------------------
1 | package org.mitre.neoprofiler.profiler;
2 |
3 | import java.util.HashMap;
4 | import java.util.HashSet;
5 | import java.util.List;
6 |
7 | import org.mitre.neoprofiler.NeoProfiler;
8 | import org.mitre.neoprofiler.profile.LabelProfile;
9 | import org.mitre.neoprofiler.profile.NeoProfile;
10 | import org.mitre.neoprofiler.profile.NeoProperty;
11 | import org.neo4j.driver.v1.Transaction;
12 | import org.neo4j.driver.v1.types.Node;
13 | import org.neo4j.driver.v1.Value;
14 |
15 |
16 | public class UnlabeledNodeProfiler extends LabelProfiler {
17 | public static final String PARAMETER_LABEL = "(Unlabeled)";
18 |
19 | public UnlabeledNodeProfiler() {
20 | super(PARAMETER_LABEL);
21 | }
22 |
23 | public NeoProfile run(NeoProfiler parent) {
24 | LabelProfile p = new LabelProfile(label);
25 |
26 | Object result = runQuerySingleResult(parent, "match (n) where labels(n)=[] return count(n) as c", "c");
27 | p.addObservation(NeoProfile.OB_COUNT, "" + result);
28 |
29 | int sampleSize = (Integer) p.getParameter("sampleSize");
30 |
31 | List nodeSamples = runQueryMultipleResult(parent, "match (n) where labels(n)=[] return n as instance limit " + sampleSize,
32 | "instance");
33 |
34 | try (Transaction tx = parent.beginTx()) {
35 | HashSet props = new HashSet();
36 | HashMap seen = new HashMap();
37 |
38 | for (Object ns : nodeSamples) {
39 | for (NeoProperty prop : getSampleProperties(parent, ((Value) ns).asNode())) {
40 | String key = prop.toString();
41 |
42 | // Increment counter.
43 | if (!seen.containsKey(key)) {
44 | seen.put(key, 1);
45 | } else {
46 | seen.put(key, seen.get(key) + 1);
47 | continue;
48 | }
49 |
50 | props.add(prop);
51 | }
52 | }
53 |
54 | // If a property was seen in some (but not all) samples, then it's optional.
55 | for (NeoProperty prop : props) {
56 | int count = seen.get(prop.toString());
57 | if (count < sampleSize) prop.setOptional(true);
58 | else prop.setOptional(false);
59 | }
60 |
61 | p.addObservation(NeoProfile.OB_SAMPLE_PROPERTIES, props);
62 | } // End try
63 |
64 | List outbound = runQueryMultipleResult(parent,
65 | "match (n)-[r]->(m) where labels(n)=[] and n <> m return distinct(type(r)) as outbound", "outbound");
66 |
67 | if (outbound.isEmpty()) outbound.add(NeoProfile.OB_VALUE_NA);
68 | p.addObservation(LabelProfile.OB_OUTBOUND_RELATIONSHIP_TYPES, outbound);
69 |
70 | List inbound = runQueryMultipleResult(parent,
71 | "match (n)<-[r]-(m) where labels(n)=[] and n <> m return distinct(type(r)) as inbound", "inbound");
72 |
73 | if (inbound.isEmpty()) inbound.add(NeoProfile.OB_VALUE_NA);
74 | p.addObservation(LabelProfile.OB_INBOUND_RELATIONSHIP_TYPES, inbound);
75 |
76 | // TODO Auto-generated method stub
77 | return p;
78 | }
79 |
80 | public String describe() {
81 | return "UnlabeledNodeProfiler";
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/java/org/mitre/neoprofiler/profiler/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | */
4 | /**
5 | * @author x
6 | *
7 | */
8 | package org.mitre.neoprofiler.profiler;
--------------------------------------------------------------------------------
/src/main/resources/.shell_history:
--------------------------------------------------------------------------------
1 | create (p:Person {name:"Bob", age:35, gender:"M" })
2 | -[:KNOWS]->(m:Person {name:"Susan", age:43, gender:"F"});
3 | create (p:Person {name:"Sam", age:23, gender:"M" })
4 | ->[:KNOWS]->(m:Person {name:"Michael", age:22, gender:"M");
5 | create (p:Person {name:"Sam", age:23, gender:"M" })
6 | -[:KNOWS]->(m:Person {name:"Michael", age:22, gender:"M");
7 | create (p:Person {name:"Sam", age:23, gender:"M" })
8 | -[:KNOWS]->(m:Person {name:"Michael", age:22, gender:"M"});
9 | match (boss:Person {name:"Susan"}), (emp:Person {name:"Sam"}),
10 | (emp2:Person {name:"Michael"})
11 | create emp-[:WORKS_FOR]->boss, emp2-[:WORKS_FOR]->boss;
12 |
--------------------------------------------------------------------------------
/src/main/resources/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $title
5 |
6 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
164 |
165 |
166 |
167 | $markdown
168 |
169 |
170 |
171 |
172 |
--------------------------------------------------------------------------------