├── .gitignore
├── CHANGES.md
├── README.md
├── circle.yml
├── project.clj
├── resources
├── om-i.css
├── om-i.less
└── om-i.min.css
└── src
└── om_i
├── core.cljs
├── hacks.cljs
└── keyboard.cljs
/.gitignore:
--------------------------------------------------------------------------------
1 | pom.xml
2 | *jar
3 | /lib/
4 | /classes/
5 | /out/
6 | /target/
7 | .lein-deps-sum
8 | .lein-repl-history
9 | .lein-plugins/
10 | /script/
11 |
--------------------------------------------------------------------------------
/CHANGES.md:
--------------------------------------------------------------------------------
1 | ## 0.1.8
2 | ### Changes
3 | * Stop depending on _rootNodeId so that we can support React 0.13.
4 |
5 | ## 0.1.7
6 | ### Changes
7 | * Fix ordering of render-ms and mount-ms
8 |
9 | ## 0.1.6
10 | ### Changes
11 | * fix borked release to Clojars of 0.1.5
12 |
13 | ## 0.1.5
14 | ### Changes
15 | * Note: this released was messed up somehow, use 0.1.6 instead
16 | * fix un-required goog.string.format. Thanks [@cldwalker](https://github.com/cldwalker)!
17 |
18 | ## 0.1.4
19 | ### Changes
20 | * fixes compilation error from requiring `goog`
21 |
22 | ## 0.1.3
23 | ### Changes
24 | * remove unused cljs-time requires
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Om-instrumentation
2 |
3 | Instrumentation helper for Om applications.
4 |
5 | [](https://circleci.com/gh/PrecursorApp/om-i)
6 |
7 | ## Overview
8 |
9 | Om-i (pronounced "Oh, my!") helps you identify the components in your [Om](https://github.com/omcljs/om) application that are being passed too much of your app state and rendering unnecessarily. It provides useful statistics about render times and frequencies for all of your components.
10 |
11 | You can see it live on [Precursor](https://precursorapp.com), a collaborative drawing application. Use Ctrl+Alt+Shift+j to toggle it.
12 |
13 |
14 |
15 |
16 |
17 | ## Setup
18 |
19 | ### Dependencies
20 | Add Om-i to your project's dependencies
21 |
22 | ```
23 | [precursor/om-i "0.1.8"]
24 | ```
25 |
26 | ### Enable the instrumentation
27 |
28 | Use Om-i's custom descriptor so that it can gather render times for your components. To enable it globally, use the `:instrument` opt in `om/root`
29 |
30 | ```clojure
31 | (om/root
32 | app-component
33 | app-state
34 | {:target container
35 | :instrument (fn [f cursor m]
36 | (om/build* f cursor
37 | (assoc m
38 | :descriptor om-i.core/instrumentation-methods)))})
39 | ```
40 |
41 | ### Mount the component
42 |
43 | Add the following somewhere in your setup code. If you're using figwheel, place it somewhere that won't get reloaded.
44 |
45 | ```clojure
46 | (om-i.core/setup-component-stats!)
47 | ```
48 |
49 | Om-i renders its statistics in a separate root so that it doesn't interact with your application.
50 |
51 | It will create a `div` in the body with classname "om-instrumentation" by default and assign three keyboard shortcuts: Ctrl+Alt+Shift+j to bring down the statistics menu, Ctrl+Alt+Shift+k to clear the statistics, and Ctrl+Alt+Shift+s to switch the sort order.
52 |
53 |
54 | You can override the defaults with:
55 |
56 | ```clojure
57 | (om-i.core/setup-component-stats! {:class "om-instrumentation"
58 | :clear-shortcut #{"ctrl" "alt" "shift" "k"}
59 | :toggle-shortcut #{"ctrl" "alt" "shift" "j"}
60 | :sort-shorcut #{"ctrl" "alt" "shift" "s"}})
61 | ```
62 |
63 | ### Styles
64 |
65 | You need to set up css styles to handle displaying the instrumentation when it's opened. There are sample less and css files in the resources directory.
66 |
67 | If you want to try out Om-i, or just use it in development, we've provided a helper that will embed a style tag with the syles from resources/om-i.min.css.
68 |
69 | ```clojure
70 | (om-i.hacks/insert-styles)
71 | ```
72 |
73 | It's not recommended to use this in production.
74 |
75 | ### Wrapping a pre-existing descriptor
76 |
77 | If you're already using a custom descriptor, you can still use Om-i. Here's an example wrapping Om's `no-local-descriptor`.
78 |
79 | ```clojure
80 | (let [methods (om-i.core/instrument-methods om/no-local-state-methods)
81 | descriptor (om/no-local-descriptor methods)]
82 | (om/root
83 | app-component
84 | app-state
85 | {:target container
86 | :instrument (fn [f cursor m]
87 | (om/build* f cursor (assoc m :descriptor descriptor)))}))
88 | ```
89 |
90 | ## Acknowledgements
91 |
92 | Thanks to [@sgrove](https://github.com/sgrove) for his keyboard handling code. Om-i uses a minimal version of the code he wrote for Precursor. There is an older, [public version of the code in Omchaya](https://github.com/sgrove/omchaya/blob/master/src/omchaya/components/key_queue.cljs).
93 |
94 | Thanks to [@brandonbloom](https://github.com/brandonbloom) for demonstrating how to use descriptors in Om. [Related blog post](http://blog.circleci.com/local-state-global-concerns/).
95 |
96 | Thanks to [@swannodette](https://github.com/swannodette) for releasing Om.
97 |
98 |
99 | ## License
100 |
101 | Copyright © 2015 PrecursorApp
102 |
103 | Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.
104 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | dependencies:
2 | post:
3 | - lein cljsbuild once
4 |
5 | test:
6 | override:
7 | - echo no tests
8 |
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject precursor/om-i "0.1.8"
2 | :description "Instrumentation helpers for Om applications"
3 | :url "https://github.com/PrecursorApp/om-i"
4 | :license {:name "Eclipse Public License"
5 | :url "http://www.eclipse.org/legal/epl-v10.html"}
6 | :dependencies [[org.clojure/clojure "1.6.0"]
7 | [org.clojure/clojurescript "0.0-2755" :scope "provided"]
8 | [org.omcljs/om "0.8.8" :scope "provided"]]
9 |
10 | :plugins [[lein-cljsbuild "1.0.4"]]
11 | :cljsbuild {:builds [{:id "test"
12 | :source-paths ["src"]
13 | :compiler {:output-to "script/tests.simple.js"
14 | :output-dir "script/out"
15 | :source-map "script/tests.simple.js.map"
16 | :output-wrapper false
17 | :optimizations :simple}}]}
18 |
19 | :source-paths ["src"])
20 |
--------------------------------------------------------------------------------
/resources/om-i.css:
--------------------------------------------------------------------------------
1 | @keyframes in-fade-top-soft {
2 | 0% {
3 | opacity: 0;
4 | transform: translate3d(0, -4rem, 0);
5 | }
6 | 100% {
7 | opacity: 1;
8 | transform: none;
9 | }
10 | }
11 | @-webkit-keyframes in-fade-top-soft {
12 | 0% {
13 | opacity: 0;
14 | -webkit-transform: translate3d(0, -4rem, 0);
15 | }
16 | 100% {
17 | opacity: 1;
18 | -webkit-transform: none;
19 | }
20 | }
21 | .om-instrumentation {
22 | user-select: none;
23 | -moz-user-select: none;
24 | -webkit-user-select: none;
25 | pointer-events: none;
26 | color: #888888;
27 | position: fixed;
28 | z-index: 1000;
29 | top: 0;
30 | left: 0;
31 | width: 100%;
32 | }
33 | .instrumentation-table {
34 | -webkit-animation: in-fade-top-soft 500ms;
35 | animation: in-fade-top-soft 500ms;
36 | background-color: rgba(0, 0, 0, 0.6);
37 | font-family: Monaco, monospace;
38 | font-size: .75rem;
39 | line-height: 2;
40 | width: 100%;
41 | }
42 | .instrumentation-table th {
43 | color: #ffffff;
44 | line-height: 1rem;
45 | padding: 1.5rem 0;
46 | text-transform: uppercase;
47 | text-align: left;
48 | }
49 | .instrumentation-table th:not(:first-child) {
50 | text-align: center;
51 | }
52 | .instrumentation-table th.left {
53 | text-align: left;
54 | }
55 | .instrumentation-table th.right {
56 | text-align: right;
57 | }
58 | .instrumentation-table th,
59 | .instrumentation-table td {
60 | white-space: pre-wrap;
61 | }
62 | .instrumentation-table th:first-child,
63 | .instrumentation-table td:first-child {
64 | padding-left: 1.5rem;
65 | }
66 | .instrumentation-table tbody tr:nth-child(odd) {
67 | background-color: rgba(0, 0, 0, 0.4);
68 | }
69 | .instrumentation-table tbody td:nth-child(even) {
70 | text-align: right;
71 | border-right: 1px dashed;
72 | padding-right: .5em;
73 | }
74 | .instrumentation-table tbody td:nth-child(odd) {
75 | text-align: left;
76 | padding-left: .5em;
77 | }
78 | .instrumentation-table tbody td:first-child {
79 | padding-left: 1.5rem;
80 | }
81 | .instrumentation-table tfoot td {
82 | line-height: 1rem;
83 | text-align: center;
84 | padding: 1.5rem 0;
85 | }
86 | .instrumentation-table small {
87 | font-size: 1em;
88 | opacity: .5;
89 | }
90 |
--------------------------------------------------------------------------------
/resources/om-i.less:
--------------------------------------------------------------------------------
1 | @black: #000;
2 | @white: #fff;
3 | @gray: #888;
4 |
5 | @font_mono: Monaco, monospace;
6 | @zindex-instrumentation: 1000;
7 |
8 | @tile: (1rem * 4);
9 | @menu_padding: 1.5rem;
10 |
11 | @keyframes in-fade-top-soft {
12 | 0% { opacity: 0; transform: translate3d(0, -@tile, 0);}
13 | 100% { opacity: 1; transform: none;}
14 | }
15 | @-webkit-keyframes in-fade-top-soft {
16 | 0% { opacity: 0; -webkit-transform: translate3d(0, -@tile, 0);}
17 | 100% { opacity: 1; -webkit-transform: none;}
18 | }
19 | .om-instrumentation {
20 | user-select: none;
21 | -moz-user-select: none;
22 | -webkit-user-select: none;
23 | pointer-events: none;
24 | color: @gray;
25 | position: fixed;
26 | z-index: @zindex-instrumentation;
27 | top: 0;
28 | left: 0;
29 | width: 100%;
30 | }
31 | .instrumentation-table {
32 | -webkit-animation: in-fade-top-soft 500ms;
33 | animation: in-fade-top-soft 500ms;
34 | background-color: fade(@black, 60);
35 | font-family: @font_mono;
36 | font-size: .75rem;
37 | line-height: 2;
38 | width: 100%;
39 | th {
40 | color: @white;
41 | line-height: 1rem;
42 | padding: @menu_padding 0;
43 | text-transform: uppercase;
44 | text-align: left;
45 | &:not(:first-child) {
46 | text-align: center;
47 | }
48 | &.left {
49 | text-align: left;
50 | }
51 | &.right {
52 | text-align: right;
53 | }
54 | }
55 | th,
56 | td {
57 | white-space: pre-wrap;
58 | &:first-child {
59 | padding-left: @menu_padding;
60 | }
61 | }
62 | tbody {
63 | tr {
64 | &:nth-child(odd) {
65 | background-color: fade(@black, 40);
66 | }
67 | }
68 | td {
69 | &:nth-child(even) {
70 | text-align: right;
71 | border-right: 1px dashed;
72 | padding-right: .5em;
73 | }
74 | &:nth-child(odd) {
75 | text-align: left;
76 | padding-left: .5em;
77 | }
78 | &:first-child {
79 | padding-left: @menu_padding;
80 | }
81 | }
82 | }
83 | tfoot {
84 | td {
85 | line-height: 1rem;
86 | text-align: center;
87 | padding: @menu_padding 0;
88 | }
89 | }
90 | small {
91 | font-size: 1em;
92 | opacity: .5;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/resources/om-i.min.css:
--------------------------------------------------------------------------------
1 | @keyframes in-fade-top-soft{0%{opacity:0;transform:translate3d(0, -4rem, 0)}100%{opacity:1;transform:none}}@-webkit-keyframes in-fade-top-soft{0%{opacity:0;-webkit-transform:translate3d(0, -4rem, 0)}100%{opacity:1;-webkit-transform:none}}.om-instrumentation{user-select:none;-moz-user-select:none;-webkit-user-select:none;pointer-events:none;color:#888;position:fixed;z-index:1000;top:0;left:0;width:100%}.instrumentation-table{-webkit-animation:in-fade-top-soft 500ms;animation:in-fade-top-soft 500ms;background-color:rgba(0,0,0,0.6);font-family:Monaco,monospace;font-size:.75rem;line-height:2;width:100%}.instrumentation-table th{color:#fff;line-height:1rem;padding:1.5rem 0;text-transform:uppercase;text-align:left}.instrumentation-table th:not(:first-child){text-align:center}.instrumentation-table th.left{text-align:left}.instrumentation-table th.right{text-align:right}.instrumentation-table th,.instrumentation-table td{white-space:pre-wrap}.instrumentation-table th:first-child,.instrumentation-table td:first-child{padding-left:1.5rem}.instrumentation-table tbody tr:nth-child(odd){background-color:rgba(0,0,0,0.4)}.instrumentation-table tbody td:nth-child(even){text-align:right;border-right:1px dashed;padding-right:.5em}.instrumentation-table tbody td:nth-child(odd){text-align:left;padding-left:.5em}.instrumentation-table tbody td:first-child{padding-left:1.5rem}.instrumentation-table tfoot td{line-height:1rem;text-align:center;padding:1.5rem 0}.instrumentation-table small{font-size:1em;opacity:.5}
--------------------------------------------------------------------------------
/src/om_i/core.cljs:
--------------------------------------------------------------------------------
1 | (ns om-i.core
2 | (:require [clojure.string :as str]
3 | [goog.dom]
4 | [goog.string :as gstring]
5 | [goog.string.format]
6 | [om.core :as om :include-macros true]
7 | [om.dom :as dom :include-macros true]
8 | [om-i.keyboard :as keyboard]))
9 |
10 | ;; map of display name to component render stats, e.g.
11 | ;; {"App" {:last-will-update