├── .gitignore ├── LICENSE ├── README.org ├── fabfile.py ├── include └── emetric.hrl ├── priv └── plotr │ ├── README │ ├── merge.py │ ├── plotr.py │ └── plotr_units.py ├── rebar ├── rebar.config └── src ├── emetric.app.src ├── emetric.erl ├── emetric_app.erl ├── emetric_appsrv.erl ├── emetric_cmd_connect.erl ├── emetric_cmd_info.erl ├── emetric_cmd_inject.erl ├── emetric_cmd_start.erl ├── emetric_cmd_stop.erl ├── emetric_command.erl ├── emetric_config.erl ├── emetric_core.erl ├── emetric_filter_csv.erl ├── emetric_hooks.erl ├── emetric_loadable.erl ├── emetric_log_file.erl ├── emetric_stats_ejd.erl ├── emetric_stats_mnesia.erl ├── emetric_stats_sys.erl ├── emetric_sup.erl ├── emetric_ticker.erl ├── emetric_util.erl └── getopt.erl /.gitignore: -------------------------------------------------------------------------------- 1 | *.beam 2 | *.pyc 3 | ebin/*.app 4 | emetric 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Justin Kirby 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of the Voalte, Inc nor the names of its 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+OPTIONS: author:nil creator:nil date:nil 2 | 3 | * Quick 4 | 5 | #+begin_example 6 | git clone git://github.com/justinkirby/emetric.git 7 | 8 | cd emetric && ./rebar compile 9 | 10 | ./emetric --node=slow@localhost --cookie=monster start gather=sys,mnesia scatter=file mods=emetric_filter_csv 11 | 12 | tail /tmp/emetric_slow@localhost_201103102159.csv 13 | 14 | ./emetric --node=slow@localhost --cookie=monster stop 15 | 16 | #+end_example 17 | 18 | * Overview 19 | 20 | emetric creates a high level view of the resources that a running 21 | erlang system consumes over time. This is useful for long running 22 | stress tests. 23 | 24 | 25 | 26 | * Build 27 | The goal is to have a single file that is easy to 'install' on any 28 | environment that has erlang. There is no complicated release install 29 | process (inspired by rebar) 30 | 31 | 32 | This is a standard rebar build 33 | 34 | #+begin_example 35 | ./rebar compile 36 | #+end_example 37 | 38 | After a successful build, there will be a single executable file, 39 | emetric. 40 | 41 | * Usage 42 | 43 | emetric will do the following: 44 | - connect to a remote node 45 | - inject the necessary modules 46 | - start emetric 47 | 48 | Each command may have dependencies. If it does then those are run 49 | first. You do not have to specify all commands in order if you simply 50 | want to start recording. 51 | 52 | Of course, there are the *help* and *version* commands, which do the 53 | obvious. 54 | 55 | ** connect 56 | This will connect to an erlang node and make sure that emetric can communicate with it. 57 | 58 | #+begin_example 59 | ./emetric --node=slow@localhost --cookie=monster connect 60 | #+end_example 61 | It is one of those lovely commands that will do nothing on success 62 | and be very noisy during failure. It does, however, return 0 on 63 | success and >=1 on failure 64 | 65 | ** inject 66 | This requires a successful connect. 67 | 68 | The modules for gathering the statistics, scattering them to files 69 | and filtering them need to be specified here. 70 | 71 | #+begin_example 72 | ./emetric --node=slow@localhost --cookie=monster inject gather=sys,mnesia scatter=file mods=emetric_filter_csv 73 | #+end_example 74 | The above will gather system wide metrics, mnesia statistics and dump them all out to a csv file in /tmp 75 | 76 | ** start 77 | This will start recording of all requested metrics and push them 78 | out to a file (easy to add more datasources) every two seconds. 79 | 80 | Supported metrics are: 81 | - system and linux 82 | - mnesia 83 | - ejabberd 84 | 85 | #+begin_example 86 | ./emetric --node=slow@localhost --cookie=monster inject gather=sys,mnesia scatter=file mods=emetric_filter_csv 87 | #+end_example 88 | 89 | The above will make sure all the appropriate modules are injected 90 | in the remote slow@localhost node. It will start the internal 91 | ticker that is triggered every two seconds to gather the metrics. 92 | 93 | Once the metrics are all gathered for that 'tick', it is sent to 94 | the scatter modules, in this case a file. The filter is applied 95 | before writing the data. 96 | 97 | The file name is created via the following template: 98 | #+begin_example 99 | /tmp/emetric_node()_YYYYMMDDHHmm.ext 100 | #+end_example 101 | Example 102 | #+begin_example 103 | /tmp/emetric_slow@localhost_201103102211.csv 104 | #+end_example 105 | 106 | ** stop 107 | The emetric will happily keep recording data until either the host 108 | node dies or you tell it to stop. (hopefully it is the latter.) 109 | 110 | #+begin_example 111 | ./emetric --node=slow@localhost --cookie=monster stop 112 | #+end_example 113 | -------------------------------------------------------------------------------- /fabfile.py: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------- 2 | # @author Justin Kirby 3 | # @copyright (C) 2011 Justin Kirby 4 | # @end 5 | # 6 | # This source file is subject to the New BSD License. You should have received 7 | # a copy of the New BSD license with this software. If not, it can be 8 | # retrieved from: http://www.opensource.org/licenses/bsd-license.php 9 | # ------------------------------------------------------------------- 10 | 11 | 12 | from fabric.api import * 13 | from datetime import datetime 14 | 15 | env.now = datetime.now().strftime("%Y%m%d%H%M") 16 | env.ver = "00.01.00" 17 | 18 | 19 | def release(version=None): 20 | dirnm = "%s/%s/%s"%(env.pdir,env.ver,env.now) 21 | ln = "%s/emetric"%(env.pdir,) 22 | run("mkdir -p %s"%(dirnm,)) 23 | put("emetric",dirnm) 24 | run("if test -e %s; then rm -f %s; fi"%(ln,ln)) 25 | run("ln -s %s %s"%(dirnm+"/emetric",ln)) 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /include/emetric.hrl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Justin Kirby 3 | %%% @copyright (C) 2011 Justin Kirby 4 | %%% @end 5 | %%% 6 | %%% This source file is subject to the New BSD License. You should have received 7 | %%% a copy of the New BSD license with this software. If not, it can be 8 | %%% retrieved from: http://www.opensource.org/licenses/bsd-license.php 9 | %%%------------------------------------------------------------------- 10 | 11 | 12 | -define(CONSOLE(Str, Args), io:format(Str,Args)). 13 | 14 | %%TODO: move following to a logger 15 | -define(DEBUG(Str, Args), io:format(Str, Args)). 16 | -define(INFO(Str, Args), io:format(Str, Args)). 17 | -define(WARN(Str, Args), io:format(Str, Args)). 18 | -define(ERROR(Str, Args), io:format(Str, Args)). 19 | 20 | 21 | %% Helper macro for declaring children of supervisor 22 | -define(CHILD(I, Type), {I, {I, start_link, []}, permanent, brutal_kill, Type, [I]}). 23 | -define(CHILD(I, Type, Arg), {I, {I, start_link, [Arg]}, permanent, brutal_kill, Type, [I]}). 24 | -------------------------------------------------------------------------------- /priv/plotr/README: -------------------------------------------------------------------------------- 1 | Note: this is intended as a playground to look at data. You probably 2 | shouldn't really try to use this. 3 | 4 | 5 | I tried to make this easy on osx, I probably failed. 6 | 7 | * Install 8 | - install port from http://www.macports.org/ 9 | - sudo port -v selfupdate 10 | - sudo port install py26-numpy 11 | - sudo port install py26-matplotlib 12 | 13 | 14 | * Run 15 | - /opt/local/bin/python2.6 emetric/priv/plotr/plotr.py 16 | --data=path/to/file.csv --out=path/to/out.csv 17 | 18 | This will generate a new csv file with all the uninteresting data 19 | filtered out. At this point you can import into a spreadsheet and 20 | make pretty graphs. Or you can make a very unsupported attempt to 21 | get pygtk installed. Then you can use the --graph option and let 22 | matplotlib to all the graphing for you. 23 | -------------------------------------------------------------------------------- /priv/plotr/merge.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # ------------------------------------------------------------------- 3 | # @author Justin Kirby 4 | # @copyright (C) 2011 Justin Kirby 5 | # @end 6 | # 7 | # This source file is subject to the New BSD License. You should have received 8 | # a copy of the New BSD license with this software. If not, it can be 9 | # retrieved from: http://www.opensource.org/licenses/bsd-license.php 10 | # ------------------------------------------------------------------- 11 | 12 | 13 | import os 14 | import sys 15 | import getopt 16 | import re 17 | 18 | import numpy as np 19 | from matplotlib import mlab 20 | 21 | 22 | def file_dict(opts): 23 | rv=[] 24 | for f in opts["file"]: 25 | uniq = re.match(opts["re"],f).group(1) 26 | rv.append({"file":f, 27 | "uniq":uniq}) 28 | return rv 29 | 30 | 31 | def append_rec(recs): 32 | base = mlab.csv2rec(recs[0]["file"]) 33 | 34 | for nw in recs[1:]: 35 | append = mlab.csv2rec(nw["file"]) 36 | for k,v in append.dtype.fields.iteritems(): 37 | base = mlab.recs_join("sys_tick",k,[base,append],missing=0) 38 | return base 39 | 40 | 41 | 42 | class Usage(Exception): 43 | def __init__(self,msg): 44 | self.msg = msg 45 | def usage(): 46 | return """ 47 | merge.py [options] 48 | 49 | This will attempt to merge csv files generated from emetric from multiple nodes 50 | 51 | -h|--help: 52 | print this help message 53 | 54 | -f file|--file=file 55 | File to append. This can be specified multiple times. The first file will be used as the base and any files after that will be appended. 56 | merge.py --file=one.csv --file=two.csv 57 | 58 | -r regex|--re=regex 59 | The regex to use to extract unique name from files. most likely: 'emetric_(.*)@.*.csv' which is the default. Note this uses the python re.match(re,filename).group(1) 60 | 61 | """ 62 | def get_options(argv): 63 | if argv is None: 64 | argv = sys.argv 65 | 66 | 67 | 68 | try: 69 | try: 70 | opts,args = getopt.getopt(argv[1:], "hf:r:", 71 | ["help","file=","re="]) 72 | except getopt.error,msg: 73 | raise Usage(msg) 74 | except Usage,err: 75 | print >>sys.stderr,err.msg 76 | print >>sys.stderr," for help use --help" 77 | return (1,{}) 78 | 79 | config = {"file":[], 80 | "re":"emetric_(.*)@.*.csv" 81 | } 82 | for o,a in opts: 83 | if o in ("-h","--help"): 84 | print usage() 85 | return (2,{}) 86 | if o in ("-f","--file"): 87 | config["file"].append(a) 88 | if o in ("-r","--re"): 89 | config["re"] = a 90 | 91 | 92 | return (0,config) 93 | 94 | 95 | def main(argv=None): 96 | 97 | print """ 98 | ******************** 99 | ******************** 100 | ******************** 101 | ******************** 102 | THIS DOES NOT WORK 103 | ******************** 104 | ******************** 105 | ******************** 106 | ******************** 107 | """ 108 | return 255 109 | rv,options = get_options(argv) 110 | if rv: return rv #we die if there was a problem 111 | 112 | if len(options["file"]) <= 0: 113 | print usage() 114 | return 2 115 | 116 | print append_rec(file_dict(options)) 117 | return 0 118 | 119 | if __name__ == "__main__": 120 | sys.exit(main()) 121 | 122 | 123 | -------------------------------------------------------------------------------- /priv/plotr/plotr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # ------------------------------------------------------------------- 3 | # @author Justin Kirby 4 | # @copyright (C) 2011 Justin Kirby 5 | # @end 6 | # 7 | # This source file is subject to the New BSD License. You should have received 8 | # a copy of the New BSD license with this software. If not, it can be 9 | # retrieved from: http://www.opensource.org/licenses/bsd-license.php 10 | # ------------------------------------------------------------------- 11 | 12 | 13 | import os 14 | import sys 15 | import getopt 16 | import itertools 17 | 18 | 19 | class DepFail(Exception): 20 | def __init__(self,pkg): 21 | self.pkg = pkg 22 | (distro,a,b,c,d) = os.uname() 23 | self.os = distro 24 | self.msgs={ 25 | "Linux":{ 26 | "gtk":"Use your linux pkg manager to install pygtk", 27 | "numpy":"Use your linux pkg manager to install numpy", 28 | "matplotlib":"Use your linux pkg manager to install matplotlib", 29 | "yaml":"User your linux pkg manager to install python yaml", 30 | }, 31 | "Darwin":{ 32 | "gtk":""" 33 | Do not bother trying to install pygtk. This is just too 34 | painful. Instead use the headless option for this program and use a 35 | tool that can importdata from csv files. 36 | """, 37 | "numpy":"Use ports to install numpy. sudo port install py26-numpy", 38 | "matplotlib":"Use ports to install matplotlib, or the dmg from 1.0 on sf.net", 39 | "yaml":"Use ports to install python yaml." 40 | } 41 | } 42 | 43 | def message(self): 44 | if self.os in self.msgs: 45 | if self.pkg in self.msgs[self.os]: 46 | return self.msgs[self.os][self.pkg] 47 | else: 48 | return "Sorry I don't know what to tell you about %s\nThis is a bug"%self.pkg 49 | else: 50 | return "Sorry I don't know anything about this OS, %s\nThis is probably not a bug"%self.os 51 | 52 | def validate_sys(opts): 53 | """ 54 | Make sure the required deps exist. Be friendly by not doing anything 55 | """ 56 | reqs=["numpy","matplotlib"] 57 | if opts["ui"]: 58 | reqs.extend(["gtk","pygtk"]) 59 | if opts["graph"]: 60 | reqs.extend(["matplotlib.pyplot"]) 61 | if "config" in opts: 62 | reqs.extend(["yaml"]) 63 | 64 | for r in reqs: 65 | try: 66 | __import__(r) 67 | except ImportError,e: 68 | raise DepFail(r) 69 | return True 70 | 71 | 72 | 73 | class Usage(Exception): 74 | def __init__(self,msg): 75 | self.msg = msg 76 | def usage(): 77 | return """ 78 | plotr.py [options] 79 | 80 | This will display a list of 'interesting' fields in which you can compare in line graphs. It works best when you only compare two metrics. If you try for more, then good luck. 81 | 82 | -h|--help: 83 | print this help message 84 | 85 | -u|--ui: 86 | Uses gtk UI and allows interaction. 87 | 88 | -g|--graph: 89 | Enables graphing using pltr. This requires matlib plot to be installed. 90 | 91 | -o file|--out=file: 92 | File to write cleaned csv data to. This is required if --graph is not specified 93 | 94 | -d file|--data=file : 95 | Specify the file to parse. This must be a csv file. Preferably one generated by emetric, though it shouldn't really matter. If no file specified, then a file chooser window will appear. You need a file, if you don't provide one, this tool just exits. 96 | 97 | -b|--boring : 98 | Include boring stuff. 99 | 100 | -c file|--config=file: 101 | Config file to use. Especially useful if not using --ui 102 | 103 | Definition of Interesting: if the standard deviation of a column is > 0. (There is some change in the value.) 104 | 105 | I am trying to find a way to make this tool useful for comparing more than two metrics. 106 | 107 | 108 | Examples: 109 | 110 | Without graphing support 111 | $python plotr.py --data=emetric_ejabberd\@localhost_123456789.csv --out=clean_emetric.csv 112 | 113 | With UI support 114 | $python plotr.py --ui --data=emetric_ejabberd\@localhost_123456789.csv 115 | """ 116 | 117 | def gtk_ui(interest,dat): 118 | 119 | import pygtk 120 | pygtk.require('2.0') 121 | import gtk 122 | 123 | class SelectWin: 124 | def __init__(self, interesting, data): 125 | self.interesting = interesting 126 | self.plotr = Plot(interesting,data) 127 | 128 | self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) 129 | self.window.set_title("The Ugly Voalte Prettiness Maker") 130 | self.window.connect("delete_event",self.delete_event) 131 | 132 | 133 | vcomp = gtk.VBox(False,2) 134 | self.window.add(vcomp) 135 | 136 | 137 | 138 | bplot = gtk.Button("Plot") 139 | bplot.connect("clicked",self.plot) 140 | vcomp.pack_start(bplot,False,True,2) 141 | 142 | bper = gtk.CheckButton("Pivot Per") 143 | bper.connect("toggled",self.toggle_per) 144 | vcomp.pack_start(bper,False,True,2) 145 | 146 | 147 | cpivot = gtk.combo_box_new_text() 148 | cpivot.connect("changed",self.change_pivot) 149 | vcomp.pack_start(cpivot,False,True,2) 150 | 151 | sz = len(self.interesting) 152 | row_cnt = 20 153 | cols_cnt = sz/row_cnt 154 | 155 | table = gtk.Table(row_cnt,cols_cnt,False) 156 | vcomp.pack_start(table,False,True,2) 157 | 158 | self.interesting.sort() 159 | col = 0 160 | for r,i in itertools.izip(itertools.cycle(range(0,20)),self.interesting): 161 | label = i.replace("_"," ") 162 | bcomp = gtk.CheckButton(label) 163 | bcomp.connect("toggled", self.toggle_interest, i) 164 | 165 | table.attach(bcomp, col,col+1, r,r+1) 166 | 167 | if r == 19: 168 | col += 1 169 | cpivot.append_text(i) 170 | 171 | 172 | 173 | self.window.show_all() 174 | 175 | def delete_event(self, widget, event, data=None): 176 | gtk.main_quit() 177 | return False 178 | 179 | def toggle_interest(self, widget, data=None): 180 | if not widget.get_active(): 181 | #remove data from list if exists 182 | self.plotr.keys_del(data) 183 | else: 184 | self.plotr.plot_key(data) 185 | 186 | def toggle_per(self,widget): 187 | self.plotr.pivot_on(widget.get_active()) 188 | 189 | def change_pivot(self,widget): 190 | model = widget.get_model() 191 | index = widget.get_active() 192 | if index >= 0: 193 | self.plotr.pivot_key(model[index][0]) 194 | else: 195 | self.plotr.pivot_key(None) 196 | 197 | 198 | def plot(self,widget,data=None): 199 | import matplotlib.pyplot as plt 200 | self.plotr.create(lambda: plt.show()) 201 | 202 | 203 | 204 | 205 | 206 | #end of class <-- this is bad, I know 207 | 208 | selwin = SelectWin(interest,dat) 209 | gtk.main() 210 | 211 | class Plot: 212 | def __init__(self, keys, data): 213 | self.keys = keys 214 | self.data = data 215 | self.fig = None 216 | self.pivot = None 217 | self.keys_plot = [] 218 | self.pivot_per = False 219 | self.colors = ['b','g','r','c','m','y','k'] 220 | self.shapes = ['.',',','o','v','^','<','>','1','2','3','4','s','p', 221 | '*','h','H','+','x','D','d','|','_'] 222 | 223 | #figure out which tick name to use, should make tick name 224 | #more findable, e.g. tick 225 | for tn in ['sys_tick','ejd_tick','mnesia_tick']: 226 | if tn in keys: 227 | self.tick_key=tn 228 | break 229 | 230 | def pivot_key(self,p): 231 | self.pivot = p 232 | 233 | def pivot_on(self,p): 234 | self.pivot_per = p 235 | 236 | 237 | def keys_del(self,k): 238 | try: 239 | del self.keys_plot[self.keys_plot.index(k)] 240 | except ValueError,err: 241 | pass 242 | 243 | def plot_key(self,k): 244 | self.keys_plot.append(k) 245 | 246 | def save(self,fnm): 247 | self.fig.savefig(fnm) 248 | 249 | def create(self,cb=None): 250 | for d in self.data: 251 | self.create_plot(d,cb) 252 | 253 | def create_plot(self,data,cb=None): 254 | import numpy as np 255 | import matplotlib.pyplot as plt 256 | from plotr_units import Units 257 | 258 | units = Units() 259 | # self.pretty_cnt = self.pretty_cnt+1 260 | 261 | 262 | # simple combinitorial product of shapes and colors so we can 263 | # iterate over them. matlib likes things such as 'r.' and 264 | # 'g-', etc.. 265 | line_style = itertools.product(self.shapes,self.colors) 266 | def next_style(): 267 | s = list(next(line_style)) 268 | s.reverse() 269 | return s 270 | 271 | 272 | kp = self.keys_plot# just to make it easier to reference 273 | 274 | self.fig = plt.figure(figsize=(15,10)) 275 | plt.subplots_adjust(hspace=0.01) 276 | 277 | 278 | x_set= data[self.tick_key] 279 | pv_info = units.info(self.pivot) 280 | 281 | 282 | pv_conv = np.frompyfunc(pv_info["convert"],1,1) 283 | pivot_set = pv_conv(data[self.pivot]) 284 | color_pivot,shape_pivot = next_style() 285 | 286 | labels_to_hide=[] 287 | 288 | for k,i in zip(kp, range(0,len(kp))): 289 | 290 | sp = self.fig.add_subplot(len(kp),1,i+1) 291 | sp.set_xlabel("ticks (s)") 292 | sp.grid(True) 293 | 294 | plt.plot(x_set,pivot_set, color_pivot+shape_pivot) 295 | sp.set_ylabel(pv_info["label"],color=color_pivot) 296 | for tl in sp.get_yticklabels(): 297 | tl.set_color(color_pivot) 298 | 299 | if not self.pivot_per: 300 | y_set = data[k] 301 | else: 302 | y_set = data[k]/data[self.pivot] 303 | color_y,shape_y = next_style() 304 | ax2 = sp.twinx() 305 | 306 | unit_info = units.info(k) 307 | y_conv = np.frompyfunc(unit_info["convert"],1,1) 308 | ax2.plot(x_set,y_conv(y_set),color_y+shape_y) 309 | 310 | 311 | label = "%s (%s)"%(unit_info["label"],unit_info["unit"]) 312 | ax2.set_ylabel(label,color=color_y) 313 | for tl in ax2.get_yticklabels(): 314 | tl.set_color(color_y) 315 | 316 | #hid all but the last ones 317 | if i < len(kp): 318 | labels_to_hide.append(sp.get_xticklabels()) 319 | 320 | plt.setp(labels_to_hide,visible=False) 321 | 322 | if cb is not None: 323 | cb() 324 | 325 | 326 | def headless(opts,interesting,data): 327 | from os import path 328 | from plotr_units import Units 329 | dr = path.abspath(path.dirname(opts["data"][0])) 330 | 331 | plots = [] 332 | units = Units() 333 | 334 | 335 | def plot_gen(pivot_key, plot_key, pivot_on): 336 | p = Plot(interesting,data) 337 | p.pivot_key(pivot_key) 338 | p.plot_key(plot_key) 339 | 340 | if pivot_on: 341 | p.pivot_on(True) 342 | p.create() 343 | p.save("%s/%s-%s.png"%(dr,pivot_key,plot_key)) 344 | plots.append({"file":"%s-%s.png"%(pivot_key,plot_key), 345 | "key":plot_key, 346 | "name":units.info(plot_key)["label"]}) 347 | 348 | 349 | import yaml 350 | if "config" in opts: 351 | cfg = yaml.load(file(opts["config"],'r')) 352 | for k,pv in cfg.iteritems(): 353 | for v in pv: 354 | plot_gen(k,v["key"],v["pivot"]) 355 | 356 | else: 357 | pivot_key = "ejd_hosts_global_sessions" 358 | for i in interesting: 359 | if i == pivot_key: 360 | continue 361 | print i 362 | plot_gen(pivot_key, i, False) 363 | 364 | from string import Template 365 | 366 | #create index.html with a toc 367 | toc = [] 368 | img = [] 369 | toc_t = Template("
  • ${name}
  • ") 370 | img_t = Template("") 371 | for p in plots: 372 | toc.append(toc_t.substitute(p)) 373 | img.append(img_t.substitute(p)) 374 | 375 | html = {"toc":"".join(toc), 376 | "img":"".join(img), 377 | "csv":path.basename(opts["data"][0]) 378 | } 379 | 380 | 381 | html_t = Template("

    CSV

      ${toc}
    ${img}
    ") 382 | with open("%s/index.html"%(dr,), 'w') as index: 383 | index.write(html_t.substitute(html)) 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | def interesting_out(opts,interesting,data): 402 | """ 403 | Take a list of fields, and the recs 404 | output recs as csv to opts["out"], e.g. --out 405 | """ 406 | header = True 407 | from matplotlib import mlab 408 | for d in data: 409 | cleaned = mlab.rec_keep_fields(d,interesting) 410 | mlab.rec2csv(cleaned,opts["out"],withheader=header) 411 | header=False 412 | 413 | 414 | 415 | 416 | def extract_interesting(options): 417 | """ 418 | grab any fields that have a std deviation > 0, 419 | e.g. they changed 420 | """ 421 | from matplotlib import mlab 422 | def extract(data): 423 | data_recs = mlab.csv2rec(data) 424 | rv=[] 425 | 426 | for k,v in data_recs.dtype.fields.iteritems(): 427 | try:#note that datetime will raise 428 | s = data_recs[k].std() 429 | if s > 0 or options["boring"]: 430 | rv.append(k) 431 | except TypeError,e: 432 | pass #non number type, e.g. date 433 | return (rv,data_recs) 434 | 435 | interesting=[] 436 | recs=[] 437 | 438 | for d in options["data"]: 439 | (interest, rec) = extract(d) 440 | interesting.extend(interest) 441 | #need to make these unique. files could have different 'interesting-ness' 442 | interesting = [k for k,v in itertools.groupby(sorted(interesting))] 443 | recs.append(rec) 444 | 445 | return (interesting,recs) 446 | 447 | 448 | 449 | 450 | 451 | def ask_for_file(options): 452 | """ 453 | If no gtk, then we just error 454 | If gtk, then we pop up a pretty dialog file chooser 455 | 456 | We are only here if --data wasn't specified 457 | """ 458 | if not options["graph"]: 459 | print >>sys.stderr,"No file specified, see --help" 460 | return (1,"") 461 | else: 462 | import pygtk 463 | pygtk.require('2.0') 464 | import gtk 465 | 466 | fs = gtk.FileChooserDialog("Open..",None, 467 | gtk.FILE_CHOOSER_ACTION_OPEN, 468 | (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, 469 | gtk.STOCK_OPEN, gtk.RESPONSE_OK)) 470 | fs.set_default_response(gtk.RESPONSE_OK) 471 | fs.set_select_multiple(True) 472 | filter = gtk.FileFilter() 473 | filter.set_name("All files") 474 | filter.add_pattern("*.*") 475 | fs.add_filter(filter) 476 | 477 | filter = gtk.FileFilter() 478 | filter.set_name("CSV") 479 | filter.add_pattern("*.csv") 480 | fs.add_filter(filter) 481 | 482 | response = fs.run() 483 | if response == gtk.RESPONSE_OK: 484 | rv = (0,fs.get_filenames()) 485 | else: 486 | rv = (1,"") 487 | fs.destroy() 488 | return rv 489 | 490 | 491 | 492 | 493 | def get_options(argv): 494 | if argv is None: 495 | argv = sys.argv 496 | 497 | try: 498 | try: 499 | opts,args = getopt.getopt(argv[1:], "hd:bgo:uc:", 500 | ["help","data=","boring", 501 | "graph","out=","ui", 502 | "config="]) 503 | except getopt.error,msg: 504 | raise Usage(msg) 505 | except Usage,err: 506 | print >>sys.stderr,err.msg 507 | print >>sys.stderr," for help use --help" 508 | return (1,{}) 509 | 510 | config = {"boring":False, 511 | "graph":False, 512 | "ui":False} 513 | for o,a in opts: 514 | if o in ("-h","--help"): 515 | print usage() 516 | return (2,{}) 517 | if o in ("-d","--data"): 518 | config["data"] = a.split(',') 519 | if o in ("-b","--boring"): 520 | config["boring"] = True 521 | if o in ("-g","--graph"): 522 | config["graph"] = True 523 | if o in("-o","--out"): 524 | config["out"] = a 525 | if o in("-u","--ui"): 526 | config["ui"] = True 527 | if o in("-c","--config"): 528 | config["config"] = a 529 | 530 | 531 | return (0,config) 532 | 533 | 534 | def main(argv=None): 535 | rv,options = get_options(argv) 536 | if rv: return rv #we die if there was a problem 537 | 538 | try: 539 | validate_sys(options) 540 | except DepFail,e: 541 | print >>sys.stderr,e.message() 542 | return 255 543 | 544 | 545 | if "data" not in options: 546 | rv,options["data"] = ask_for_file(options) 547 | if rv: return rv 548 | 549 | (interesting,data) = extract_interesting(options) 550 | 551 | if options["ui"]: 552 | gtk_ui(interesting,data) 553 | elif options["graph"]: 554 | headless(options,interesting,data) 555 | else: 556 | if "out" not in options: 557 | print >>sys.stderr,"You need to specify --out file if not running with graph, see --help" 558 | return 1 559 | interesting_out(options,interesting,data) 560 | 561 | return 0 562 | 563 | if __name__ == "__main__": 564 | sys.exit(main()) 565 | 566 | 567 | -------------------------------------------------------------------------------- /priv/plotr/plotr_units.py: -------------------------------------------------------------------------------- 1 | 2 | def _bytes_kb(b): return b/1024.0 if b != 0 else 0 3 | def _words_kb(w): return _bytes_kb(w*8.0) 4 | def _none(n): return n 5 | 6 | class Units: 7 | def info(self,key): 8 | if key in self.units: 9 | rv = self.units[key] 10 | if rv["unit"] is None: 11 | rv["unit"] = "" 12 | if rv["convert"] is None: 13 | rv["convert"] = _none 14 | if rv["label"] is None: 15 | rv["label"] = key 16 | return rv 17 | else: 18 | return {"unit":"","convert":_none,"label":key} 19 | def __init__(self): 20 | 21 | self.units = { 22 | "ejd_tick":{"unit":None,"convert":None,"label":"Tick"}, 23 | "ejd_hosts_global_now":{"unit":None,"convert":None,"label":"Time"}, 24 | "ejd_hosts_global_sessions":{"unit":None,"convert":None,"label":"Global Ejabberd Sessions"}, 25 | "ejd_hosts_vsvoasrqsd01.voalte.net_users_total":{"unit":None,"convert":None,"label":"vsvoasrqsd01.voalte.net Total Users"}, 26 | "ejd_hosts_vsvoasrqsd01.voalte.net_users_online":{"unit":None,"convert":None,"label":"vsvoasrqsd01.voalte.net Online Users"}, 27 | "sys_tick":{"unit":None,"convert":None,"label":"Tick"}, 28 | "sys_node":{"unit":None,"convert":None,"label":"Node"}, 29 | "sys_total_ram":{"unit":"KB","convert":_bytes_kb,"label":"Total RAM"}, 30 | "sys_cores":{"unit":None,"convert":None,"label":"Number of CPU cores"}, 31 | "sys_now":{"unit":None,"convert":None,"label":"Time"}, 32 | "sys_procs":{"unit":None,"convert":None,"label":"Erlang Process Count" }, 33 | "sys_context_switches":{"unit":None,"convert":None,"label":"Erlang Context Switches" }, 34 | "sys_gcs":{"unit":None,"convert":None,"label":"Number of Garbage Collextion Runs"}, 35 | "sys_gc_reclaimed":{"unit":"KB","convert":_words_kb,"label":"Words reclaimed"}, 36 | "sys_io_in":{"unit":"KB","convert":_bytes_kb,"label":"Port Input"}, 37 | "sys_io_out":{"unit":"KB","convert":_bytes_kb,"label":"Port Output"}, 38 | "sys_reductions":{"unit":None,"convert":None,"label":"Total Reductions"}, 39 | "sys_reducts_since":{"unit":None,"convert":None,"label":"Reductions Since"}, 40 | "sys_run_queue":{"unit":None,"convert":None,"label":"Process Read to Run (run queue)"}, 41 | "sys_total":{"unit":"KB","convert":_bytes_kb,"label":"Total Memory Alloc"}, 42 | "sys_processes":{"unit":"KB","convert":_bytes_kb,"label":"Memory Alloc'd by Erlang Processes"}, 43 | "sys_processes_used":{"unit":"KB","convert":_bytes_kb,"label":"Memory Used by Erlang Processes"}, 44 | "sys_system":{"unit":"KB","convert":_bytes_kb,"label":"Memory Alloc by Erlang Emulator"}, 45 | "sys_atom":{"unit":"KB","convert":_bytes_kb,"label":"Memory Alloc for Erlang Atoms"}, 46 | "sys_atom_used":{"unit":"KB","convert":_bytes_kb,"label":"Memory Used by Erlang Atoms"}, 47 | "sys_binary":{"unit":"KB","convert":_bytes_kb,"label":"Memory Alloc'd for Erlang Binary"}, 48 | "sys_code":{"unit":"KB","convert":_bytes_kb,"label":"Memory Alloc'd for Erlang Code"}, 49 | "sys_ets":{"unit":"KB","convert":_bytes_kb,"label":"Memory Alloc'd for erts"}, 50 | "sys_user":{"unit":"second","convert":None,"label":"CPU User Time"}, 51 | "sys_nice":{"unit":"second","convert":None,"label":"CPU Nice Time"}, 52 | "sys_kernel":{"unit":"second","convert":None,"label":"CPU Kernel Time"}, 53 | "sys_idle":{"unit":"second","convert":None,"label":"CPU Idle Time"}, 54 | "sys_iowait":{"unit":"second","convert":None,"label":"CPU iowait"}, 55 | "sys_beam_user":{"unit":"second","convert":None,"label":"BEAM User Time"}, 56 | "sys_beam_kernel":{"unit":"second","convert":None,"label":"BEAM Kernel Time"}, 57 | "sys_beam_vss":{"unit":"KB","convert":_bytes_kb,"label":"BEAM vss"}, 58 | "sys_beam_rss":{"unit":"KB","convert": _bytes_kb,"label":"BEAM rss"}, 59 | "sys_beam_minflt":{"unit":None,"convert":None,"label":"Minor Faults/sec"}, 60 | "sys_beam_majflt":{"unit":None,"convert":None,"label":"Major Faults/sec"}, 61 | "sys_mem_total":{"unit":"KB","convert":_bytes_kb,"label":"Total Memory"}, 62 | "sys_mem_free":{"unit":"KB","convert":_bytes_kb,"label":"Memory Free"}, 63 | "sys_buffers":{"unit":"KB","convert":_bytes_kb,"label":"File Buffers"}, 64 | "sys_cached":{"unit":"KB","convert":_bytes_kb,"label":"Page Cache"}, 65 | "sys_swap_cached":{"unit":"KB","convert":_bytes_kb,"label":"Swap Cache"}, 66 | "sys_active":{"unit":"KB","convert":_bytes_kb,"label":"Active Page Cache"}, 67 | "sys_inactive":{"unit":"KB","convert":_bytes_kb,"label":"Inactive Page Cache"}, 68 | "sys_high_total":{"unit":"KB","convert":_bytes_kb,"label":"Memory High Total"}, 69 | "sys_high_free":{"unit":"KB","convert":_bytes_kb,"label":"Memory High Free"}, 70 | "sys_low_total":{"unit":"KB","convert":_bytes_kb,"label":"Memory Low Total"}, 71 | "sys_low_free":{"unit":"KB","convert":_bytes_kb,"label":"Memory Low Free"}, 72 | "sys_swap_total":{"unit":"KB","convert":_bytes_kb,"label":"Swap Total"}, 73 | "sys_swap_free":{"unit":"KB","convert":_bytes_kb,"label":"Swap Free"}, 74 | "sys_dirty":{"unit":"KB","convert":_bytes_kb,"label":"Memory Dirty"}, 75 | "sys_write_back":{"unit":"KB","convert":_bytes_kb,"label":"Memory Write Back"}, 76 | "sys_anon_pages":{"unit":"KB","convert":_bytes_kb,"label":"Memory Anon Pages"}, 77 | "sys_mapped":{"unit":"KB","convert":_bytes_kb,"label":"Memory Mapped"}, 78 | "sys_slab":{"unit":"KB","convert":_bytes_kb,"label":"Memory SLAB"}, 79 | "sys_page_tables":{"unit":"KB","convert":_bytes_kb,"label":"Memory Page Tables"}, 80 | "sys_nfs_unstable":{"unit":"KB","convert":_bytes_kb,"label":"Memory NFS Unstable"}, 81 | "sys_bounce":{"unit":"KB","convert":_bytes_kb,"label":"Memory Bounce"}, 82 | "sys_commit_limit":{"unit":"KB","convert":_bytes_kb,"label":"Memory Commit Limit"}, 83 | "sys_committed_as":{"unit":"KB","convert":_bytes_kb,"label":"Memory Commit AS"}, 84 | "sys_vmalloc_total":{"unit":"KB","convert":_bytes_kb,"label":"Memory vmalloc Total"}, 85 | "sys_vmalloc_used":{"unit":"KB","convert":_bytes_kb,"label":"Memory vmalloc Used"}, 86 | "sys_vmalloc_chunk":{"unit":"KB","convert":_bytes_kb,"label":"Memory vmalloc Chunk"}, 87 | "sys_hugepagesize":{"unit":"KB","convert":_bytes_kb,"label":"Memory Huge Page Size"}, 88 | "sys_lo_recv_bytes":{"unit":"KB","convert":_bytes_kb,"label":"Net lo recv"}, 89 | "sys_lo_recv_packets":{"unit":"KB","convert":_bytes_kb,"label":"Net lo recv packets"}, 90 | "sys_lo_recv_errs":{"unit":None,"convert":None,"label":"Net lo recv errors"}, 91 | "sys_lo_recv_drop":{"unit":None,"convert":None,"label":"Net lo recv drop errors"}, 92 | "sys_lo_recv_fifo":{"unit":None,"convert":None,"label":"Net lo recv fifo errors"}, 93 | "sys_lo_recv_frame":{"unit":None,"convert":None,"label":"Net lo recv frame errors"}, 94 | "sys_lo_recv_compressed":{"unit":None,"convert":None,"label":"Net lo recv compressed packets"}, 95 | "sys_lo_recv_multicast":{"unit":None,"convert":None,"label":"Net lo recv multicast frames"}, 96 | "sys_lo_trans_bytes":{"unit":"KB","convert":_bytes_kb,"label":"Net lo trans"}, 97 | "sys_lo_trans_packets":{"unit":None,"convert":None,"label":"Net lo trans packets"}, 98 | "sys_lo_trans_errs":{"unit":None,"convert":None,"label":"Net lo trans errors"}, 99 | "sys_lo_trans_drop":{"unit":None,"convert":None,"label":"Net lo trans drop errors"}, 100 | "sys_lo_trans_fifo":{"unit":None,"convert":None,"label":"Net lo trans fifo errors"}, 101 | "sys_lo_trans_colls":{"unit":None,"convert":None,"label":"Net lo trans collisions"}, 102 | "sys_lo_trans_carrier":{"unit":None,"convert":None,"label":"Net lo trans carrier errors"}, 103 | "sys_lo_trans_compressed":{"unit":None,"convert":None,"label":"Net lo trans compressed packets"}, 104 | "sys_eth0_recv_bytes":{"unit":"KB","convert":_bytes_kb,"label":"Net eth0 recv"}, 105 | "sys_eth0_recv_packets":{"unit":None,"convert":None,"label":None}, 106 | "sys_eth0_recv_errs":{"unit":None,"convert":None,"label":None}, 107 | "sys_eth0_recv_drop":{"unit":None,"convert":None,"label":None}, 108 | "sys_eth0_recv_fifo":{"unit":None,"convert":None,"label":None}, 109 | "sys_eth0_recv_frame":{"unit":None,"convert":None,"label":None}, 110 | "sys_eth0_recv_compressed":{"unit":None,"convert":None,"label":None}, 111 | "sys_eth0_recv_multicast":{"unit":None,"convert":None,"label":None}, 112 | "sys_eth0_trans_bytes":{"unit":"KB","convert":_bytes_kb,"label":"Net eth0 trans"}, 113 | "sys_eth0_trans_packets":{"unit":None,"convert":None,"label":None}, 114 | "sys_eth0_trans_errs":{"unit":None,"convert":None,"label":None}, 115 | "sys_eth0_trans_drop":{"unit":None,"convert":None,"label":None}, 116 | "sys_eth0_trans_fifo":{"unit":None,"convert":None,"label":None}, 117 | "sys_eth0_trans_colls":{"unit":None,"convert":None,"label":None}, 118 | "sys_eth0_trans_carrier":{"unit":None,"convert":None,"label":None}, 119 | "sys_eth0_trans_compressed":{"unit":None,"convert":None,"label":None}, 120 | "sys_eth1_recv_bytes":{"unit":"KB","convert":_bytes_kb,"label":"Net eth1 recv"}, 121 | "sys_eth1_recv_packets":{"unit":None,"convert":None,"label":None}, 122 | "sys_eth1_recv_errs":{"unit":None,"convert":None,"label":None}, 123 | "sys_eth1_recv_drop":{"unit":None,"convert":None,"label":None}, 124 | "sys_eth1_recv_fifo":{"unit":None,"convert":None,"label":None}, 125 | "sys_eth1_recv_frame":{"unit":None,"convert":None,"label":None}, 126 | "sys_eth1_recv_compressed":{"unit":None,"convert":None,"label":None}, 127 | "sys_eth1_recv_multicast":{"unit":None,"convert":None,"label":None}, 128 | "sys_eth1_trans_bytes":{"unit":"KB","convert":_bytes_kb,"label":"Net eth1 trans"}, 129 | "sys_eth1_trans_packets":{"unit":None,"convert":None,"label":None}, 130 | "sys_eth1_trans_errs":{"unit":None,"convert":None,"label":None}, 131 | "sys_eth1_trans_drop":{"unit":None,"convert":None,"label":None}, 132 | "sys_eth1_trans_fifo":{"unit":None,"convert":None,"label":None}, 133 | "sys_eth1_trans_colls":{"unit":None,"convert":None,"label":None}, 134 | "sys_eth1_trans_carrier":{"unit":None,"convert":None,"label":None}, 135 | "sys_eth1_trans_compressed":{"unit":None,"convert":None,"label":None}, 136 | "sys_tun0_recv_bytes":{"unit":"KB","convert":_bytes_kb,"label":"Net tun0 recv"}, 137 | "sys_tun0_recv_packets":{"unit":None,"convert":None,"label":None}, 138 | "sys_tun0_recv_errs":{"unit":None,"convert":None,"label":None}, 139 | "sys_tun0_recv_drop":{"unit":None,"convert":None,"label":None}, 140 | "sys_tun0_recv_fifo":{"unit":None,"convert":None,"label":None}, 141 | "sys_tun0_recv_frame":{"unit":None,"convert":None,"label":None}, 142 | "sys_tun0_recv_compressed":{"unit":None,"convert":None,"label":None}, 143 | "sys_tun0_recv_multicast":{"unit":None,"convert":None,"label":None}, 144 | "sys_tun0_trans_bytes":{"unit":"KB","convert":_bytes_kb,"label":"Net tun0 trans"}, 145 | "sys_tun0_trans_packets":{"unit":None,"convert":None,"label":None}, 146 | "sys_tun0_trans_errs":{"unit":None,"convert":None,"label":None}, 147 | "sys_tun0_trans_drop":{"unit":None,"convert":None,"label":None}, 148 | "sys_tun0_trans_fifo":{"unit":None,"convert":None,"label":None}, 149 | "sys_tun0_trans_colls":{"unit":None,"convert":None,"label":None}, 150 | "sys_tun0_trans_carrier":{"unit":None,"convert":None,"label":None}, 151 | "sys_tun0_trans_compressed":{"unit":None,"convert":None,"label":None}, 152 | "mnesia_system_held_locks":{"unit":None,"convert":None,"label":"Mnesia Held Locks"}, 153 | "mnesia_system_lock_queue":{"unit":None,"convert":None,"label":"Mnesia lock queue"}, 154 | "mnesia_system_subscribers":{"unit":None,"convert":None,"label":"Mnesia subscribers"}, 155 | "mnesia_system_tables":{"unit":None,"convert":None,"label":"Mnesia tables"}, 156 | "mnesia_system_transactions":{"unit":None,"convert":None,"label":None}, 157 | "mnesia_system_transaction_failures":{"unit":None,"convert":None,"label":None}, 158 | "mnesia_system_transaction_commits":{"unit":None,"convert":None,"label":None}, 159 | "mnesia_system_transaction_restarts":{"unit":None,"convert":None,"label":None}, 160 | "mnesia_system_transaction_log_writes":{"unit":None,"convert":None,"label":None}, 161 | "mnesia_table_voalte_caps_info_checkpoints":{"unit":None,"convert":None,"label":None}, 162 | "mnesia_table_voalte_caps_info_subscribers":{"unit":None,"convert":None,"label":None}, 163 | "mnesia_table_voalte_caps_info_memory":{"unit":"KB","convert":_words_kb,"label":None}, 164 | "mnesia_table_voalte_caps_info_size":{"unit":None,"convert":None,"label":None}, 165 | "mnesia_table_pubsub_subscription_checkpoints":{"unit":None,"convert":None,"label":None}, 166 | "mnesia_table_pubsub_subscription_subscribers":{"unit":None,"convert":None,"label":None}, 167 | "mnesia_table_pubsub_subscription_memory":{"unit":"KB","convert":_words_kb,"label":None}, 168 | "mnesia_table_pubsub_subscription_size":{"unit":None,"convert":None,"label":None}, 169 | "mnesia_table_roster_version_checkpoints":{"unit":None,"convert":None,"label":None}, 170 | "mnesia_table_roster_version_subscribers":{"unit":None,"convert":None,"label":None}, 171 | "mnesia_table_roster_version_memory":{"unit":"KB","convert":_words_kb,"label":None}, 172 | "mnesia_table_roster_version_size":{"unit":None,"convert":None,"label":None,"label":None}, 173 | "mnesia_table_session_checkpoints":{"unit":None,"convert":None,"label":None,"label":None}, 174 | "mnesia_table_session_subscribers":{"unit":None,"convert":None,"label":None,"label":None}, 175 | "mnesia_table_session_memory":{"unit":"KB","convert":_words_kb,"label":None}, 176 | "mnesia_table_session_size":{"unit":None,"convert":None,"label":None}, 177 | "mnesia_table_irc_custom_checkpoints":{"unit":None,"convert":None,"label":None}, 178 | "mnesia_table_irc_custom_subscribers":{"unit":None,"convert":None,"label":None}, 179 | "mnesia_table_irc_custom_memory":{"unit":"KB","convert":_words_kb,"label":None}, 180 | "mnesia_table_irc_custom_size":{"unit":None,"convert":None,"label":None}, 181 | "mnesia_table_pubsub_last_item_checkpoints":{"unit":None,"convert":None,"label":None}, 182 | "mnesia_table_pubsub_last_item_subscribers":{"unit":None,"convert":None,"label":None}, 183 | "mnesia_table_pubsub_last_item_memory":{"unit":"KB","convert":_words_kb,"label":None}, 184 | "mnesia_table_pubsub_last_item_size":{"unit":None,"convert":None,"label":None}, 185 | "mnesia_table_pubsub_item_checkpoints":{"unit":None,"convert":None,"label":None}, 186 | "mnesia_table_pubsub_item_subscribers":{"unit":None,"convert":None,"label":None}, 187 | "mnesia_table_pubsub_item_memory":{"unit":"KB","convert":_words_kb,"label":None}, 188 | "mnesia_table_pubsub_item_size":{"unit":None,"convert":None,"label":None}, 189 | "mnesia_table_muc_room_checkpoints":{"unit":None,"convert":None,"label":None}, 190 | "mnesia_table_muc_room_subscribers":{"unit":None,"convert":None,"label":None}, 191 | "mnesia_table_muc_room_memory":{"unit":"KB","convert":_words_kb,"label":None}, 192 | "mnesia_table_muc_room_size":{"unit":None,"convert":None,"label":None}, 193 | "mnesia_table_muc_online_room_checkpoints":{"unit":None,"convert":None,"label":None}, 194 | "mnesia_table_muc_online_room_subscribers":{"unit":None,"convert":None,"label":None}, 195 | "mnesia_table_muc_online_room_memory":{"unit":"KB","convert":_words_kb,"label":None}, 196 | "mnesia_table_muc_online_room_size":{"unit":None,"convert":None,"label":None}, 197 | "mnesia_table_acl_checkpoints":{"unit":None,"convert":None,"label":None}, 198 | "mnesia_table_acl_subscribers":{"unit":None,"convert":None,"label":None}, 199 | "mnesia_table_acl_memory":{"unit":"KB","convert":_words_kb,"label":None}, 200 | "mnesia_table_acl_size":{"unit":None,"convert":None,"label":None}, 201 | "mnesia_table_privacy_checkpoints":{"unit":None,"convert":None,"label":None,"label":None}, 202 | "mnesia_table_privacy_subscribers":{"unit":None,"convert":None,"label":None,"label":None}, 203 | "mnesia_table_privacy_memory":{"unit":"KB","convert":_words_kb,"label":None}, 204 | "mnesia_table_privacy_size":{"unit":None,"convert":None,"label":None}, 205 | "mnesia_table_last_activity_checkpoints":{"unit":None,"convert":None,"label":None}, 206 | "mnesia_table_last_activity_subscribers":{"unit":None,"convert":None,"label":None}, 207 | "mnesia_table_last_activity_memory":{"unit":"KB","convert":_words_kb,"label":None}, 208 | "mnesia_table_last_activity_size":{"unit":None,"convert":None,"label":None}, 209 | "mnesia_table_mod_msg_delivery_checkpoints":{"unit":None,"convert":None,"label":None}, 210 | "mnesia_table_mod_msg_delivery_subscribers":{"unit":None,"convert":None,"label":None}, 211 | "mnesia_table_mod_msg_delivery_memory":{"unit":"KB","convert":_words_kb,"label":None}, 212 | "mnesia_table_mod_msg_delivery_size":{"unit":None,"convert":None,"label":None}, 213 | "mnesia_table_pubsub_index_checkpoints":{"unit":None,"convert":None,"label":None}, 214 | "mnesia_table_pubsub_index_subscribers":{"unit":None,"convert":None,"label":None}, 215 | "mnesia_table_pubsub_index_memory":{"unit":"KB","convert":_words_kb,"label":None,"label":None}, 216 | "mnesia_table_pubsub_index_size":{"unit":None,"convert":None,"label":None}, 217 | "mnesia_table_vcard_search_checkpoints":{"unit":None,"convert":None,"label":None}, 218 | "mnesia_table_vcard_search_subscribers":{"unit":None,"convert":None,"label":None}, 219 | "mnesia_table_vcard_search_memory":{"unit":"KB","convert":_words_kb,"label":None,"label":None}, 220 | "mnesia_table_vcard_search_size":{"unit":None,"convert":None,"label":None}, 221 | "mnesia_table_config_checkpoints":{"unit":None,"convert":None,"label":None}, 222 | "mnesia_table_config_subscribers":{"unit":None,"convert":None,"label":None}, 223 | "mnesia_table_config_memory":{"unit":"KB","convert":_words_kb,"label":None}, 224 | "mnesia_table_config_size":{"unit":None,"convert":None,"label":None}, 225 | "mnesia_table_local_config_checkpoints":{"unit":None,"convert":None,"label":None}, 226 | "mnesia_table_local_config_subscribers":{"unit":None,"convert":None,"label":None}, 227 | "mnesia_table_local_config_memory":{"unit":"KB","convert":_words_kb,"label":None,"label":None}, 228 | "mnesia_table_local_config_size":{"unit":None,"convert":None,"label":None}, 229 | "mnesia_table_mod_vp_config_checkpoints":{"unit":None,"convert":None,"label":None}, 230 | "mnesia_table_mod_vp_config_subscribers":{"unit":None,"convert":None,"label":None}, 231 | "mnesia_table_mod_vp_config_memory":{"unit":None,"convert":None,"label":None}, 232 | "mnesia_table_mod_vp_config_size":{"unit":None,"convert":None,"label":None}, 233 | "mnesia_table_offline_msg_checkpoints":{"unit":None,"convert":None,"label":None}, 234 | "mnesia_table_offline_msg_subscribers":{"unit":None,"convert":None,"label":None}, 235 | "mnesia_table_offline_msg_memory":{"unit":None,"convert":None,"label":None}, 236 | "mnesia_table_offline_msg_size":{"unit":None,"convert":None,"label":None}, 237 | "mnesia_table_route_checkpoints":{"unit":None,"convert":None,"label":None}, 238 | "mnesia_table_route_subscribers":{"unit":None,"convert":None,"label":None}, 239 | "mnesia_table_route_memory":{"unit":None,"convert":None,"label":None}, 240 | "mnesia_table_route_size":{"unit":None,"convert":None,"label":None}, 241 | "mnesia_table_mod_virtual_presence_checkpoints":{"unit":None,"convert":None,"label":None}, 242 | "mnesia_table_mod_virtual_presence_subscribers":{"unit":None,"convert":None,"label":None}, 243 | "mnesia_table_mod_virtual_presence_memory":{"unit":None,"convert":None,"label":None}, 244 | "mnesia_table_mod_virtual_presence_size":{"unit":None,"convert":None,"label":None}, 245 | "mnesia_table_archive_message_checkpoints":{"unit":None,"convert":None,"label":None}, 246 | "mnesia_table_archive_message_subscribers":{"unit":None,"convert":None,"label":None}, 247 | "mnesia_table_archive_message_memory":{"unit":None,"convert":None,"label":None}, 248 | "mnesia_table_archive_message_size":{"unit":None,"convert":None,"label":None}, 249 | "mnesia_table_private_storage_checkpoints":{"unit":None,"convert":None,"label":None}, 250 | "mnesia_table_private_storage_subscribers":{"unit":None,"convert":None,"label":None}, 251 | "mnesia_table_private_storage_memory":{"unit":None,"convert":None,"label":None}, 252 | "mnesia_table_private_storage_size":{"unit":None,"convert":None,"label":None}, 253 | "mnesia_table_pubsub_state_checkpoints":{"unit":None,"convert":None,"label":None}, 254 | "mnesia_table_pubsub_state_subscribers":{"unit":None,"convert":None,"label":None}, 255 | "mnesia_table_pubsub_state_memory":{"unit":None,"convert":None,"label":None,"label":None}, 256 | "mnesia_table_pubsub_state_size":{"unit":None,"convert":None,"label":None}, 257 | "mnesia_table_iq_response_checkpoints":{"unit":None,"convert":None,"label":None}, 258 | "mnesia_table_iq_response_subscribers":{"unit":None,"convert":None,"label":None}, 259 | "mnesia_table_iq_response_memory":{"unit":None,"convert":None,"label":None}, 260 | "mnesia_table_iq_response_size":{"unit":None,"convert":None,"label":None}, 261 | "mnesia_table_pubsub_node_checkpoints":{"unit":None,"convert":None,"label":None}, 262 | "mnesia_table_pubsub_node_subscribers":{"unit":None,"convert":None,"label":None}, 263 | "mnesia_table_pubsub_node_memory":{"unit":None,"convert":None,"label":None}, 264 | "mnesia_table_pubsub_node_size":{"unit":None,"convert":None,"label":None}, 265 | "mnesia_table_http_bind_checkpoints":{"unit":None,"convert":None,"label":None}, 266 | "mnesia_table_http_bind_subscribers":{"unit":None,"convert":None,"label":None}, 267 | "mnesia_table_http_bind_memory":{"unit":None,"convert":None,"label":None}, 268 | "mnesia_table_http_bind_size":{"unit":None,"convert":None,"label":None}, 269 | "mnesia_table_motd_checkpoints":{"unit":None,"convert":None,"label":None}, 270 | "mnesia_table_motd_subscribers":{"unit":None,"convert":None,"label":None}, 271 | "mnesia_table_motd_memory":{"unit":None,"convert":None,"label":None}, 272 | "mnesia_table_motd_size":{"unit":None,"convert":None,"label":None}, 273 | "mnesia_table_vcard_checkpoints":{"unit":None,"convert":None,"label":None}, 274 | "mnesia_table_vcard_subscribers":{"unit":None,"convert":None,"label":None}, 275 | "mnesia_table_vcard_memory":{"unit":None,"convert":None,"label":None}, 276 | "mnesia_table_vcard_size":{"unit":None,"convert":None,"label":None}, 277 | "mnesia_table_mod_voalte_apns_id_checkpoints":{"unit":None,"convert":None,"label":None}, 278 | "mnesia_table_mod_voalte_apns_id_subscribers":{"unit":None,"convert":None,"label":None}, 279 | "mnesia_table_mod_voalte_apns_id_memory":{"unit":None,"convert":None,"label":None}, 280 | "mnesia_table_mod_voalte_apns_id_size":{"unit":None,"convert":None,"label":None}, 281 | "mnesia_table_muc_registered_checkpoints":{"unit":None,"convert":None,"label":None}, 282 | "mnesia_table_muc_registered_subscribers":{"unit":None,"convert":None,"label":None}, 283 | "mnesia_table_muc_registered_memory":{"unit":None,"convert":None,"label":None}, 284 | "mnesia_table_muc_registered_size":{"unit":None,"convert":None,"label":None,"label":None}, 285 | "mnesia_table_archive_options_checkpoints":{"unit":None,"convert":None,"label":None}, 286 | "mnesia_table_archive_options_subscribers":{"unit":None,"convert":None,"label":None}, 287 | "mnesia_table_archive_options_memory":{"unit":None,"convert":None,"label":None}, 288 | "mnesia_table_archive_options_size":{"unit":None,"convert":None,"label":None}, 289 | "mnesia_table_s2s_checkpoints":{"unit":None,"convert":None,"label":None}, 290 | "mnesia_table_s2s_subscribers":{"unit":None,"convert":None,"label":None}, 291 | "mnesia_table_s2s_memory":{"unit":None,"convert":None,"label":None}, 292 | "mnesia_table_s2s_size":{"unit":None,"convert":None,"label":None}, 293 | "mnesia_table_voalte_offline_stats_checkpoints":{"unit":None,"convert":None,"label":None}, 294 | "mnesia_table_voalte_offline_stats_subscribers":{"unit":None,"convert":None,"label":None}, 295 | "mnesia_table_voalte_offline_stats_memory":{"unit":None,"convert":None,"label":None}, 296 | "mnesia_table_voalte_offline_stats_size":{"unit":None,"convert":None,"label":None}, 297 | "mnesia_table_caps_features_checkpoints":{"unit":None,"convert":None,"label":None}, 298 | "mnesia_table_caps_features_subscribers":{"unit":None,"convert":None,"label":None}, 299 | "mnesia_table_caps_features_memory":{"unit":None,"convert":None,"label":None}, 300 | "mnesia_table_caps_features_size":{"unit":None,"convert":None,"label":None}, 301 | "mnesia_table_motd_users_checkpoints":{"unit":None,"convert":None,"label":None}, 302 | "mnesia_table_motd_users_subscribers":{"unit":None,"convert":None,"label":None}, 303 | "mnesia_table_motd_users_memory":{"unit":None,"convert":None,"label":None}, 304 | "mnesia_table_motd_users_size":{"unit":None,"convert":None,"label":None}, 305 | "mnesia_table_voalte_stats_checkpoints":{"unit":None,"convert":None,"label":None}, 306 | "mnesia_table_voalte_stats_subscribers":{"unit":None,"convert":None,"label":None}, 307 | "mnesia_table_voalte_stats_memory":{"unit":None,"convert":None,"label":None,"label":None}, 308 | "mnesia_table_voalte_stats_size":{"unit":None,"convert":None,"label":None}, 309 | "mnesia_table_roster_checkpoints":{"unit":None,"convert":None,"label":None}, 310 | "mnesia_table_roster_subscribers":{"unit":None,"convert":None,"label":None}, 311 | "mnesia_table_roster_memory":{"unit":None,"convert":None,"label":None}, 312 | "mnesia_table_roster_size":{"unit":None,"convert":None,"label":None}, 313 | "mnesia_table_sr_user_checkpoints":{"unit":None,"convert":None,"label":None,"label":None}, 314 | "mnesia_table_sr_user_subscribers":{"unit":None,"convert":None,"label":None,"label":None}, 315 | "mnesia_table_sr_user_memory":{"unit":None,"convert":None,"label":None}, 316 | "mnesia_table_sr_user_size":{"unit":None,"convert":None,"label":None}, 317 | "mnesia_table_vs_counter_checkpoints":{"unit":None,"convert":None,"label":None}, 318 | "mnesia_table_vs_counter_subscribers":{"unit":None,"convert":None,"label":None}, 319 | "mnesia_table_vs_counter_memory":{"unit":None,"convert":None,"label":None}, 320 | "mnesia_table_vs_counter_size":{"unit":None,"convert":None,"label":None}, 321 | "mnesia_table_session_counter_checkpoints":{"unit":None,"convert":None,"label":None}, 322 | "mnesia_table_session_counter_subscribers":{"unit":None,"convert":None,"label":None}, 323 | "mnesia_table_session_counter_memory":{"unit":None,"convert":None,"label":None}, 324 | "mnesia_table_session_counter_size":{"unit":None,"convert":None,"label":None}, 325 | "mnesia_table_voalte_caps_ver_checkpoints":{"unit":None,"convert":None,"label":None}, 326 | "mnesia_table_voalte_caps_ver_subscribers":{"unit":None,"convert":None,"label":None}, 327 | "mnesia_table_voalte_caps_ver_memory":{"unit":None,"convert":None,"label":None}, 328 | "mnesia_table_voalte_caps_ver_size":{"unit":None,"convert":None,"label":None}, 329 | "mnesia_table_captcha_checkpoints":{"unit":None,"convert":None,"label":None,"label":None}, 330 | "mnesia_table_captcha_subscribers":{"unit":None,"convert":None,"label":None,"label":None}, 331 | "mnesia_table_captcha_memory":{"unit":None,"convert":None,"label":None}, 332 | "mnesia_table_captcha_size":{"unit":None,"convert":None,"label":None}, 333 | "mnesia_table_schema_checkpoints":{"unit":None,"convert":None,"label":None}, 334 | "mnesia_table_schema_subscribers":{"unit":None,"convert":None,"label":None}, 335 | "mnesia_table_schema_memory":{"unit":None,"convert":None,"label":None}, 336 | "mnesia_table_schema_size":{"unit":None,"convert":None,"label":None}, 337 | "mnesia_table_voalte_offline_iq_checkpoints":{"unit":None,"convert":None,"label":None}, 338 | "mnesia_table_voalte_offline_iq_subscribers":{"unit":None,"convert":None,"label":None}, 339 | "mnesia_table_voalte_offline_iq_memory":{"unit":None,"convert":None,"label":None}, 340 | "mnesia_table_voalte_offline_iq_size":{"unit":None,"convert":None,"label":None}, 341 | "mnesia_table_mod_vp_name_map_checkpoints":{"unit":None,"convert":None,"label":None}, 342 | "mnesia_table_mod_vp_name_map_subscribers":{"unit":None,"convert":None,"label":None}, 343 | "mnesia_table_mod_vp_name_map_memory":{"unit":None,"convert":None,"label":None}, 344 | "mnesia_table_mod_vp_name_map_size":{"unit":None,"convert":None,"label":None}, 345 | "mnesia_table_sr_group_checkpoints":{"unit":None,"convert":None,"label":None}, 346 | "mnesia_table_sr_group_subscribers":{"unit":None,"convert":None,"label":None}, 347 | "mnesia_table_sr_group_memory":{"unit":None,"convert":None,"label":None}, 348 | "mnesia_table_sr_group_size":{"unit":None,"convert":None,"label":None}, 349 | "mnesia_table_mod_register_ip_checkpoints":{"unit":None,"convert":None,"label":None}, 350 | "mnesia_table_mod_register_ip_subscribers":{"unit":None,"convert":None,"label":None}, 351 | "mnesia_table_mod_register_ip_memory":{"unit":None,"convert":None,"label":None}, 352 | "mnesia_table_mod_register_ip_size":{"unit":None,"convert":None,"label":None}, 353 | "mnesia_table_mod_vp_vocc_map_checkpoints":{"unit":None,"convert":None,"label":None}, 354 | "mnesia_table_mod_vp_vocc_map_subscribers":{"unit":None,"convert":None,"label":None}, 355 | "mnesia_table_mod_vp_vocc_map_memory":{"unit":None,"convert":None,"label":None}, 356 | "mnesia_table_mod_vp_vocc_map_size":{"unit":None,"convert":None,"label":None}, 357 | } 358 | 359 | def test(): 360 | import numpy as np 361 | u = Units() 362 | 363 | i = u.info("sys_beam_rss") 364 | 365 | conv = np.frompyfunc(i["convert"],1,1) 366 | 367 | 368 | 369 | 370 | if __name__ == "__main__": 371 | test() 372 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinkirby/emetric/6310f8f74a787100eb9d6356d2beffabc91fda1e/rebar -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/emetric.app.src: -------------------------------------------------------------------------------- 1 | {application, emetric, 2 | [ 3 | {description, "Erlang Metrics Logger"}, 4 | {vsn, "1"}, 5 | {applications, [ 6 | kernel, 7 | stdlib, 8 | sasl 9 | ]}, 10 | {modules, [ 11 | emetric, 12 | emetric_core, 13 | emetric_config, 14 | emetric_util, 15 | emetric_appsrv, 16 | emetric_cmd_connect, 17 | emetric_cmd_inject, 18 | emetric_cmd_start, 19 | emetric_cmd_stop, 20 | emetric_cmd_info, 21 | emetric_stat_sys, 22 | emetric_stat_ejd, 23 | emetric_stat_mnesia, 24 | emetric_filter_csv, 25 | getopt 26 | ] }, 27 | {registered, []}, 28 | {env, [ 29 | {commands, [ 30 | connect, 31 | inject, 32 | start, 33 | stop, 34 | info 35 | ]}, 36 | {command_prefix, "emetric_cmd_"}, 37 | {gather_prefix, "emetric_stats_"}, 38 | {scatter_prefix, "emetric_log_"}, 39 | {filter_prefix, "emetric_filter_"}, 40 | %% list of modules to start for the gather per tick. This 41 | %% will most likely be emetric_stats_* 42 | {gather, [ 43 | ]}, 44 | %% list of modules to start of the scatter per tick, This 45 | %% will most likely be emetric_log_* 46 | {scatter, [ 47 | ]}, 48 | {filter, [ 49 | ]}, 50 | {base_mods, [emetric_appsrv, 51 | emetric_sup, 52 | emetric_ticker, 53 | emetric_hooks, 54 | emetric_util 55 | ]}, 56 | {mods,[]} 57 | 58 | ]} 59 | ]}. 60 | -------------------------------------------------------------------------------- /src/emetric.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Justin Kirby 3 | %%% @copyright (C) 2011 Justin Kirby 4 | %%% @end 5 | %%% 6 | %%% This source file is subject to the New BSD License. You should have received 7 | %%% a copy of the New BSD license with this software. If not, it can be 8 | %%% retrieved from: http://www.opensource.org/licenses/bsd-license.php 9 | %%%------------------------------------------------------------------- 10 | 11 | -module(emetric). 12 | 13 | -export([main/1]). 14 | 15 | main(Args) -> 16 | case catch(emetric_core:run(Args)) of 17 | ok -> 18 | ok; 19 | {error, failed} -> 20 | halt(1); 21 | Error -> 22 | io:format("Uncaught error in emetric_core: ~p\n",[Error]), 23 | halt(1) 24 | end. 25 | -------------------------------------------------------------------------------- /src/emetric_app.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Justin Kirby 3 | %%% @copyright (C) 2011 Justin Kirby 4 | %%% @end 5 | %%% 6 | %%% This source file is subject to the New BSD License. You should have received 7 | %%% a copy of the New BSD license with this software. If not, it can be 8 | %%% retrieved from: http://www.opensource.org/licenses/bsd-license.php 9 | %%%------------------------------------------------------------------- 10 | 11 | -module(emetric_app). 12 | 13 | -behaviour(application). 14 | 15 | %% Application callbacks 16 | -export([start/2, stop/1]). 17 | 18 | %% =================================================================== 19 | %% Application callbacks 20 | %% =================================================================== 21 | 22 | start(_StartType, StartArgs) -> 23 | emetric_sup:start_link(StartArgs). 24 | 25 | stop(_State) -> 26 | ok. 27 | -------------------------------------------------------------------------------- /src/emetric_appsrv.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Justin Kirby 3 | %%% @copyright (C) 2011 Justin Kirby 4 | %%% @end 5 | %%% 6 | %%% This source file is subject to the New BSD License. You should have received 7 | %%% a copy of the New BSD license with this software. If not, it can be 8 | %%% retrieved from: http://www.opensource.org/licenses/bsd-license.php 9 | %%%------------------------------------------------------------------- 10 | 11 | -module(emetric_appsrv). 12 | 13 | -behaviour(gen_server). 14 | -behaviour(emetric_loadable). 15 | 16 | %% API 17 | -export([start/1,start_link/0,stop/0,deps/0,sup/0,run/1]). 18 | 19 | %% gen_server callbacks 20 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 21 | terminate/2, code_change/3]). 22 | 23 | -define(SERVER, ?MODULE). 24 | 25 | -record(state, {env,sup}). 26 | 27 | %%%=================================================================== 28 | %%% API 29 | %%%=================================================================== 30 | deps() -> []. 31 | sup() -> []. 32 | run(Specs) -> 33 | gen_server:call(?SERVER,{start,Specs}). 34 | start(Env) -> 35 | gen_server:start({local,?SERVER}, ?MODULE, [Env],[]). 36 | stop() -> 37 | gen_server:call(?SERVER, stop). 38 | %%-------------------------------------------------------------------- 39 | %% @doc 40 | %% Starts the server 41 | %% 42 | %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} 43 | %% @end 44 | %%-------------------------------------------------------------------- 45 | start_link() -> 46 | gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). 47 | 48 | %%%=================================================================== 49 | %%% gen_server callbacks 50 | %%%=================================================================== 51 | 52 | %%-------------------------------------------------------------------- 53 | %% @private 54 | %% @doc 55 | %% Initializes the server 56 | %% 57 | %% @spec init(Args) -> {ok, State} | 58 | %% {ok, State, Timeout} | 59 | %% ignore | 60 | %% {stop, Reason} 61 | %% @end 62 | %%-------------------------------------------------------------------- 63 | init([Env]) -> 64 | {ok, #state{env = Env}}. 65 | 66 | %%-------------------------------------------------------------------- 67 | %% @private 68 | %% @doc 69 | %% Handling call messages 70 | %% 71 | %% @spec handle_call(Request, From, State) -> 72 | %% {reply, Reply, State} | 73 | %% {reply, Reply, State, Timeout} | 74 | %% {noreply, State} | 75 | %% {noreply, State, Timeout} | 76 | %% {stop, Reason, Reply, State} | 77 | %% {stop, Reason, State} 78 | %% @end 79 | %%-------------------------------------------------------------------- 80 | handle_call({start,Specs}, _From, State) -> 81 | {ok, Sup } = emetric_sup:start_link(Specs), 82 | {reply, ok, State#state{sup=Sup}}; 83 | handle_call(stop, _From, State) -> 84 | {stop,normal,State}; 85 | handle_call(_Request, _From, State) -> 86 | Reply = ok, 87 | {reply, Reply, State}. 88 | 89 | %%-------------------------------------------------------------------- 90 | %% @private 91 | %% @doc 92 | %% Handling cast messages 93 | %% 94 | %% @spec handle_cast(Msg, State) -> {noreply, State} | 95 | %% {noreply, State, Timeout} | 96 | %% {stop, Reason, State} 97 | %% @end 98 | %%-------------------------------------------------------------------- 99 | handle_cast(_Msg, State) -> 100 | {noreply, State}. 101 | 102 | %%-------------------------------------------------------------------- 103 | %% @private 104 | %% @doc 105 | %% Handling all non call/cast messages 106 | %% 107 | %% @spec handle_info(Info, State) -> {noreply, State} | 108 | %% {noreply, State, Timeout} | 109 | %% {stop, Reason, State} 110 | %% @end 111 | %%-------------------------------------------------------------------- 112 | handle_info(_Info, State) -> 113 | {noreply, State}. 114 | 115 | %%-------------------------------------------------------------------- 116 | %% @private 117 | %% @doc 118 | %% This function is called by a gen_server when it is about to 119 | %% terminate. It should be the opposite of Module:init/1 and do any 120 | %% necessary cleaning up. When it returns, the gen_server terminates 121 | %% with Reason. The return value is ignored. 122 | %% 123 | %% @spec terminate(Reason, State) -> void() 124 | %% @end 125 | %%-------------------------------------------------------------------- 126 | terminate(_Reason, _State) -> 127 | ok. 128 | 129 | %%-------------------------------------------------------------------- 130 | %% @private 131 | %% @doc 132 | %% Convert process state when code is changed 133 | %% 134 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} 135 | %% @end 136 | %%-------------------------------------------------------------------- 137 | code_change(_OldVsn, State, _Extra) -> 138 | {ok, State}. 139 | 140 | %%%=================================================================== 141 | %%% Internal functions 142 | %%%=================================================================== 143 | -------------------------------------------------------------------------------- /src/emetric_cmd_connect.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Justin Kirby 3 | %%% @copyright (C) 2011 Justin Kirby 4 | %%% @end 5 | %%% 6 | %%% This source file is subject to the New BSD License. You should have received 7 | %%% a copy of the New BSD license with this software. If not, it can be 8 | %%% retrieved from: http://www.opensource.org/licenses/bsd-license.php 9 | %%%------------------------------------------------------------------- 10 | 11 | -module(emetric_cmd_connect). 12 | 13 | -behaviour(emetric_command). 14 | 15 | -export([ command_help/0, 16 | deps/0, 17 | run/0 18 | ]). 19 | 20 | 21 | 22 | command_help() -> 23 | {"connect","","Connect to node and respond with result"}. 24 | 25 | 26 | deps() -> []. 27 | 28 | run() -> 29 | %% if there is a cookie, change ourse 30 | Cookie = list_to_atom(emetric_config:get_global(cookie)), 31 | Node = list_to_atom(emetric_config:get_global(node)), 32 | ok = ping(Node,Cookie). 33 | 34 | 35 | 36 | 37 | ping(Node,Cookie) -> 38 | %%escript doesn't start this 39 | {ok,_Pid} = net_kernel:start(['emetric@localhost',shortnames]), 40 | 41 | %% it is possible to have the names out of sync in epmd 42 | %% need to wait for the names to get worked out 43 | global:sync(), 44 | 45 | erlang:set_cookie(node(),Cookie), 46 | pong = net_adm:ping(Node), 47 | 48 | ok. 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/emetric_cmd_info.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Justin Kirby 3 | %%% @copyright (C) 2011 Justin Kirby 4 | %%% @end 5 | %%% 6 | %%% This source file is subject to the New BSD License. You should have received 7 | %%% a copy of the New BSD license with this software. If not, it can be 8 | %%% retrieved from: http://www.opensource.org/licenses/bsd-license.php 9 | %%%------------------------------------------------------------------- 10 | 11 | -module(emetric_cmd_info). 12 | 13 | -behaviour(emetric_command). 14 | 15 | -export([command_help/0, 16 | deps/0, 17 | run/0 18 | ]). 19 | 20 | 21 | command_help()-> 22 | {"info",[],"Display the status and/or result of executed commands"}. 23 | 24 | deps() -> []. 25 | 26 | run() -> 27 | Injected = emetric_config:get_global(injected), 28 | AllMods = emetric_config:get_modules(), 29 | io:format("injected: ~p~n",[Injected]), 30 | io:format("all: ~p~n",[AllMods]). 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/emetric_cmd_inject.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Justin Kirby 3 | %%% @copyright (C) 2011 Justin Kirby 4 | %%% @end 5 | %%% 6 | %%% This source file is subject to the New BSD License. You should have received 7 | %%% a copy of the New BSD license with this software. If not, it can be 8 | %%% retrieved from: http://www.opensource.org/licenses/bsd-license.php 9 | %%%------------------------------------------------------------------- 10 | 11 | -module(emetric_cmd_inject). 12 | 13 | -behaviour(emetric_command). 14 | 15 | -export([command_help/0, 16 | deps/0, 17 | run/0 18 | ]). 19 | 20 | command_help()-> 21 | {"inject","mods=m1,mN gather=m1,mN scatter=s1,sN","Inject modules and their deps into the remote node."}. 22 | 23 | deps() -> [emetric_cmd_connect]. 24 | 25 | run() -> 26 | Node = list_to_atom(emetric_config:get_global(node)), 27 | %% List = [list_to_atom("emetric_"++M) || M <- string:tokens(emetric_config:get_global(mods),",")], 28 | List = emetric_config:get_modules(), 29 | Injected = inject_mod(Node,List,[]), 30 | 31 | emetric_config:set_global(injected,Injected), 32 | 33 | ok. 34 | 35 | inject_mod(_Node,[],Done) -> Done; 36 | 37 | inject_mod(Node,[Mod|Rest],Done) -> 38 | case lists:member(Mod, Done) of 39 | true -> inject_mod(Node,Rest,Done); 40 | false -> 41 | case lists:member({deps,0},Mod:module_info(exports)) of 42 | %% does not have deps exported, assume it is vanilla 43 | %% with no deps 44 | false -> 45 | ok = assert_loaded(Node,Mod), 46 | inject_mod(Node,Rest,Done++[Mod]); 47 | true -> 48 | ok = assert_loaded(Node,Mod), 49 | Deps = Mod:deps(), 50 | inject_mod(Node,Deps++Rest,Done++[Mod]) 51 | end 52 | end. 53 | 54 | 55 | assert_loaded(Node,Mod) -> 56 | case rpc:call(Node,Mod,module_info,[compile]) of 57 | {badrpc,{'EXIT', {undef,_}}} -> 58 | netload(Node,Mod), 59 | assert_loaded(Node,Mod); 60 | {badrpc,_} -> 61 | ok; 62 | CompInfo when is_list(CompInfo) -> 63 | case {ftime(CompInfo),ftime(Mod:module_info(compile))} of 64 | {interpreted,_} -> 65 | ok; 66 | {TargT, HostT} when TargT < HostT -> 67 | netload(Node, Mod), 68 | assert_loaded(Node,Mod); 69 | _ -> 70 | ok 71 | end 72 | end. 73 | 74 | netload(Node,Mod) -> 75 | {Mod, Bin,Fname} = code:get_object_code(Mod), 76 | {module, Mod} = rpc:call(Node, code, load_binary, [Mod,Fname,Bin]). 77 | 78 | ftime([]) -> interpreted; 79 | ftime([{time,T}|_]) -> T; 80 | ftime([_|T]) -> ftime(T). 81 | -------------------------------------------------------------------------------- /src/emetric_cmd_start.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Justin Kirby 3 | %%% @copyright (C) 2011 Justin Kirby 4 | %%% @end 5 | %%% 6 | %%% This source file is subject to the New BSD License. You should have received 7 | %%% a copy of the New BSD license with this software. If not, it can be 8 | %%% retrieved from: http://www.opensource.org/licenses/bsd-license.php 9 | %%%------------------------------------------------------------------- 10 | 11 | 12 | -module(emetric_cmd_start). 13 | 14 | -behaviour(emetric_command). 15 | 16 | -export([command_help/0, 17 | deps/0, 18 | run/0 19 | ]). 20 | 21 | 22 | command_help() -> 23 | {"start","skip=true|false","Start the metric ticker on --node. "}. 24 | 25 | deps() -> 26 | case emetric_config:get_global(skip,"false") of 27 | "true" -> 28 | []; 29 | "false" -> 30 | [emetric_cmd_inject] 31 | end. 32 | 33 | run() -> 34 | Node = list_to_atom(emetric_config:get_global(node)), 35 | ok = supervise(Node), 36 | start_app(Node), 37 | ok. 38 | 39 | 40 | supervise(Node) -> 41 | 42 | Env = application:get_env(emetric), 43 | 44 | %% get a list of base modules that can be supervised. 45 | %% we will have race conditions if we don't stage the start 46 | Supers = lists:filter(fun(M) -> 47 | emetric_util:mod_is_supervisable(M) 48 | end,emetric_config:get_global(base_mods)), 49 | %% we need to resolve dependencies 50 | Ordered = lists:reverse(resolve_load_order(Supers,[])), 51 | %% now that we have a list of mods, get their specs 52 | Specs = lists:foldl(fun(M,Acc) -> 53 | case M:sup() of 54 | [] -> Acc; 55 | S -> [S|Acc] 56 | end end,[],Ordered), 57 | 58 | 59 | %% tell the injected app to start. This is done because rpc spawns 60 | %% a process that exits. If start_link is called then we end up 61 | %% with a killed supervisor/gen_server. for convenience and 62 | %% sanity push the env from application over to the remote node. 63 | emetric_util:rpc_ok_pid(Node,emetric_appsrv,start,[Env], 64 | fun(Error) -> 65 | io:format("ERROR: failed to start: ~p~n",[Error]), 66 | halt(1) 67 | end, 68 | fun(_Pid) -> ok end), 69 | emetric_util:rpc_ok(Node,emetric_appsrv,run,[Specs], 70 | fun(Error) -> 71 | io:format("ERROR: failed to run, ~p~n",[Error]), 72 | halt(1) 73 | end, 74 | fun() -> io:format("running~n",[]) end), 75 | ok. 76 | 77 | start_app(Node) -> 78 | Gather = supervisable_mods(emetric_config:get_global(gather)), 79 | Scatter = supervisable_mods(emetric_config:get_global(scatter)), 80 | 81 | OnError = fun(Error) -> 82 | io:format("ERROR: adding child, ~p~n",[Error]) 83 | end, 84 | OnOk = fun(_Pid) -> ok end, 85 | 86 | lists:foreach(fun(Mod) -> 87 | emetric_util:rpc_ok_pid(Node,supervisor,start_child,[emetric_sup,Mod:sup()], 88 | OnError,OnOk) 89 | end,lists:append(Gather,Scatter)), 90 | ok. 91 | 92 | 93 | 94 | 95 | 96 | 97 | resolve_load_order([],Acc) -> Acc; 98 | resolve_load_order([Mod|Rest],Acc) -> 99 | case lists:member(Mod,Acc) of 100 | true -> resolve_load_order(Rest,Acc); 101 | false -> 102 | case Mod:deps() of 103 | [] -> resolve_load_order(Rest,[Mod|Acc]); 104 | Deps -> 105 | NewAcc = resolve_load_order(Deps,Acc)++[Mod], 106 | resolve_load_order(Rest,NewAcc) 107 | end 108 | end. 109 | 110 | 111 | supervisable_mods(L) -> 112 | lists:filter(fun(M) -> 113 | emetric_util:mod_is_supervisable(M) 114 | end, L). 115 | -------------------------------------------------------------------------------- /src/emetric_cmd_stop.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Justin Kirby 3 | %%% @copyright (C) 2011 Justin Kirby 4 | %%% @end 5 | %%% 6 | %%% This source file is subject to the New BSD License. You should have received 7 | %%% a copy of the New BSD license with this software. If not, it can be 8 | %%% retrieved from: http://www.opensource.org/licenses/bsd-license.php 9 | %%%------------------------------------------------------------------- 10 | 11 | 12 | -module(emetric_cmd_stop). 13 | 14 | -behaviour(emetric_command). 15 | 16 | -export([command_help/0, 17 | deps/0, 18 | run/0 19 | ]). 20 | 21 | 22 | command_help() -> 23 | {"stop","skip=true|false","Shutdown the emetric app on the --node "}. 24 | 25 | deps() -> 26 | case emetric_config:get_global(skip,"false") of 27 | "true" -> 28 | []; 29 | "false" -> 30 | [emetric_cmd_inject] 31 | end. 32 | 33 | run() -> 34 | Node = list_to_atom(emetric_config:get_global(node)), 35 | rpc:call(Node,emetric_appsrv,stop,[]), 36 | ok. 37 | -------------------------------------------------------------------------------- /src/emetric_command.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Justin Kirby 3 | %%% @copyright (C) 2011 Justin Kirby 4 | %%% @end 5 | %%% 6 | %%% This source file is subject to the New BSD License. You should have received 7 | %%% a copy of the New BSD license with this software. If not, it can be 8 | %%% retrieved from: http://www.opensource.org/licenses/bsd-license.php 9 | %%%------------------------------------------------------------------- 10 | 11 | 12 | -module(emetric_command). 13 | 14 | 15 | -export([behaviour_info/1]). 16 | 17 | behaviour_info(callbacks) -> 18 | [{command_help,0}, 19 | {deps,0}, 20 | {run,0} 21 | ]; 22 | behaviour_info(_Other) -> 23 | undefined. 24 | -------------------------------------------------------------------------------- /src/emetric_config.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Justin Kirby 3 | %%% @copyright (C) 2011 Justin Kirby 4 | %%% @end 5 | %%% 6 | %%% This source file is subject to the New BSD License. You should have received 7 | %%% a copy of the New BSD license with this software. If not, it can be 8 | %%% retrieved from: http://www.opensource.org/licenses/bsd-license.php 9 | %%%------------------------------------------------------------------- 10 | 11 | 12 | -module(emetric_config). 13 | 14 | -export([set_global/2, 15 | get_global/2, 16 | get_global/1, 17 | get_modules/0 18 | ]). 19 | 20 | -include("emetric.hrl"). 21 | 22 | 23 | 24 | 25 | set_global(gather, Value) -> 26 | set_module_list(gather,Value); 27 | set_global(scatter,Value) -> 28 | set_module_list(scatter,Value); 29 | set_global(filter,Value) -> 30 | set_module_list(filter,Value); 31 | set_global(mods,Value) -> 32 | Mods = build_module_list("",Value), 33 | application:set_env(emetric,mods,Mods); 34 | set_global(Key,Value) when is_integer(Value) -> 35 | application:set_env(emetric, Key, erlang:max(1,Value)); 36 | set_global(Key, Value) -> 37 | application:set_env(emetric, Key, Value). 38 | 39 | 40 | get_global(Key,Default) -> 41 | case application:get_env(emetric, Key) of 42 | undefined -> 43 | Default; 44 | {ok,Value} -> 45 | Value 46 | end. 47 | 48 | get_global(Key) -> 49 | get_global(Key,undefined). 50 | 51 | get_modules() -> 52 | Keys = [base_mods,mods,gather,scatter,filter], 53 | get_modules(Keys,[]). 54 | 55 | get_modules([],Acc) -> Acc; 56 | get_modules([Key|Rest],Acc) -> 57 | Mods = get_global(Key,[]), 58 | get_modules(Rest,Acc++Mods). 59 | 60 | 61 | 62 | set_module_list(Base,Value) -> 63 | Key = list_to_atom(atom_to_list(Base) ++ "_prefix"), 64 | Pre = get_global(Key), 65 | Mods = build_module_list(Pre,Value), 66 | application:set_env(emetric,Base,Mods). 67 | 68 | 69 | build_module_list(Prefix,Args) -> 70 | build_module_list(Prefix,string:tokens(Args,","),[]). 71 | build_module_list(_Prefix,[],Mods) -> 72 | Mods; 73 | build_module_list(Prefix,[Base|Rest],Mods) -> 74 | Mod = list_to_atom(Prefix++Base), 75 | build_module_list(Prefix,Rest,Mods++[Mod]). 76 | 77 | -------------------------------------------------------------------------------- /src/emetric_core.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Justin Kirby 3 | %%% @copyright (C) 2011 Justin Kirby 4 | %%% @end 5 | %%% 6 | %%% This source file is subject to the New BSD License. You should have received 7 | %%% a copy of the New BSD license with this software. If not, it can be 8 | %%% retrieved from: http://www.opensource.org/licenses/bsd-license.php 9 | %%%------------------------------------------------------------------- 10 | 11 | 12 | 13 | -module(emetric_core). 14 | 15 | -include("emetric.hrl"). 16 | 17 | -export([run/1]). 18 | 19 | -ifndef(BUILD_TIME). 20 | -define(BUILD_TIME, "undefined"). 21 | -endif. 22 | 23 | -ifndef(VCS_INFO). 24 | -define(VCS_INFO, "undefined"). 25 | -endif. 26 | 27 | run(["help"]) -> 28 | ok = application:load(emetric), 29 | help(), 30 | ok; 31 | 32 | run(["version"]) -> 33 | ok = application:load(emetric), 34 | version(), 35 | ok; 36 | 37 | run(Args) -> 38 | ok = application:load(emetric), 39 | 40 | Commands = parse_args(Args), 41 | 42 | ok = crypto:start(), 43 | %% start logger when we have it 44 | 45 | application:start(emetric), 46 | CmdPre = emetric_config:get_global(command_prefix), 47 | 48 | CommandAtoms = [list_to_atom(CmdPre++C) || C <- Commands], 49 | 50 | process_commands(CommandAtoms), 51 | 52 | ok. 53 | 54 | process_commands(Commands) -> 55 | process_commands(Commands,[]). 56 | 57 | process_commands([],History) -> History; 58 | 59 | process_commands([Command|Rest],History) -> 60 | case lists:member(Command,History) of 61 | true -> process_commands(Rest,History); 62 | false -> 63 | Pre = Command:deps(), 64 | NewHistory = process_commands(Pre,History), 65 | Command:run(), 66 | process_commands(Rest,[Command|NewHistory]) 67 | end. 68 | 69 | 70 | 71 | parse_args(Args) -> 72 | OptSpecList = option_spec_list(), 73 | case getopt:parse(OptSpecList,Args) of 74 | {ok,{Options,NonOptArgs}} -> 75 | {ok,continue} = show_info_maybe_halt(Options,NonOptArgs), 76 | options_set(Options), 77 | filter_flags(NonOptArgs,[]); 78 | {error, {Reason,Data}} -> 79 | ?ERROR("Error: ~s ~p~nAn",[Reason,Data]), 80 | help(), 81 | halt(1) 82 | end. 83 | 84 | show_info_maybe_halt(Opts, NonOptArgs) -> 85 | case proplists:get_bool(help, Opts) of 86 | true -> 87 | help(), 88 | halt(0); 89 | false -> 90 | case proplists:get_bool(version,Opts) of 91 | true -> 92 | version(), 93 | halt(0); 94 | false -> 95 | case NonOptArgs of 96 | [] -> 97 | ?CONSOLE("No command specified!~n",[]), 98 | help(), 99 | halt(1); 100 | _ -> 101 | {ok,continue} 102 | end 103 | end 104 | end. 105 | 106 | options_set([]) -> 107 | ok; 108 | options_set([Opt|Rest]) -> 109 | case Opt of 110 | {Key,Value} -> 111 | emetric_config:set_global(Key,Value); 112 | Key -> 113 | emetric_config:set_global(Key,1) 114 | end, 115 | options_set(Rest). 116 | 117 | 118 | filter_flags([],Commands) -> 119 | lists:reverse(Commands); 120 | filter_flags([Item | Rest], Commands) -> 121 | case string:tokens(Item, "=") of 122 | [Command] -> 123 | filter_flags(Rest, [Command | Commands]); 124 | [KeyStr, Value] -> 125 | Key = list_to_atom(KeyStr), 126 | emetric_config:set_global(Key,Value), 127 | filter_flags(Rest,Commands); 128 | Other -> 129 | ?CONSOLE("Ignoring command line argument: ~p\n",[Other]), 130 | filter_flags(Rest,Commands) 131 | end. 132 | 133 | 134 | version() -> 135 | {ok, Vsn} = application:get_key(emetric,vsn), 136 | ?CONSOLE("emetric vesion: ~s~n",[Vsn]). 137 | 138 | help() -> 139 | OptSpecList = option_spec_list(), 140 | getopt:usage(OptSpecList, "emetric", 141 | "[var=value ....] [command,....]", 142 | [{"var=value","emetric global variables (e.g. cookie=foo)"}, 143 | {"command,...","Command to run (e.g. inject)"}]), 144 | commands_usage(). 145 | 146 | commands_usage() -> 147 | 148 | CommandModules = lists:map(fun(C)-> 149 | M = list_to_atom("emetric_cmd_"++atom_to_list(C)), 150 | M:command_help() 151 | end, emetric_config:get_global(commands)), 152 | lists:foreach(fun({Cmd,Args,Desc}) -> 153 | io:format(" ~-15.. s~s~n ~s~n~n",[Cmd,Args,Desc]) 154 | end,CommandModules), 155 | ok. 156 | 157 | 158 | option_spec_list() -> 159 | [ 160 | {help, $h, "help", undefined, "Display help message."}, 161 | {node, $n, "node", string, "erlang node to connect to."}, 162 | {cookie, $c, "cookie", string, "erlang cookie for remote node"} 163 | ]. 164 | -------------------------------------------------------------------------------- /src/emetric_filter_csv.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Justin Kirby 3 | %%% @copyright (C) 2011 Justin Kirby 4 | %%% @end 5 | %%% 6 | %%% This source file is subject to the New BSD License. You should have received 7 | %%% a copy of the New BSD license with this software. If not, it can be 8 | %%% retrieved from: http://www.opensource.org/licenses/bsd-license.php 9 | %%%------------------------------------------------------------------- 10 | 11 | 12 | -module(emetric_filter_csv). 13 | 14 | 15 | -export([type/0, 16 | header/1, 17 | row/1 18 | ]). 19 | 20 | type() -> "csv". 21 | 22 | header(Tick) -> 23 | string:join(lists:reverse(header(Tick,[])),","). 24 | 25 | row(Tick) -> 26 | string:join(lists:reverse(row(Tick,[])),","). 27 | 28 | 29 | 30 | header([],Acc) -> Acc; 31 | header([{Type,Stats}|Tick],Acc) -> 32 | Pre = atom_to_list(Type), 33 | NewAcc = Acc ++ header_stat(Stats,Pre,[]), 34 | header(Tick,NewAcc). 35 | 36 | header_stat([],_Pre,Acc) -> Acc; 37 | header_stat({Name,[{Key,Val}|Rest]},Pre,Acc) -> 38 | NewPre = new_pre(Pre,Name), 39 | header_stat([{Key,Val}|Rest],NewPre,Acc); 40 | header_stat([{Name,Val}|Rest],Pre,Acc) -> 41 | header_stat(Rest,Pre,header_stat({Name,Val},Pre,Acc)); 42 | header_stat({Name,_Val},Pre,Acc) -> 43 | El = new_pre(Pre,Name), 44 | [El|Acc]. 45 | 46 | new_pre(Pre,Name) when is_list(Name)-> 47 | lists:flatten(io_lib:format("~s_~s",[Pre,Name])); 48 | new_pre(Pre,Name) -> 49 | lists:flatten(io_lib:format("~s_~p",[Pre,Name])). 50 | 51 | 52 | 53 | row([],Acc) -> Acc; 54 | row([{_Type,Stats}|Tick],Acc) -> 55 | row(Tick, Acc ++ row_stat(Stats,[])). 56 | 57 | row_stat([],Acc) -> Acc; 58 | row_stat({_Name,[{Key,Val}|Rest]},Acc) -> 59 | row_stat([{Key,Val}|Rest],Acc); 60 | row_stat([{Name,Val}|Rest],Acc) -> 61 | row_stat(Rest,row_stat({Name,Val},Acc)); 62 | row_stat({now,Val},Acc) -> 63 | Time = calendar:now_to_universal_time(Val), 64 | NVal = lists:flatten(io_lib:format("~s",[emetric_util:iso_8601_fmt(Time)])), 65 | [NVal|Acc]; 66 | row_stat({_Key,Val},Acc) -> 67 | NVal = lists:flatten(io_lib:format("~p",[Val])), 68 | [NVal|Acc]. 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /src/emetric_hooks.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Justin Kirby 3 | %%% @copyright (C) 2011 Justin Kirby 4 | %%% @end 5 | %%% 6 | %%% This source file is subject to the New BSD License. You should have received 7 | %%% a copy of the New BSD license with this software. If not, it can be 8 | %%% retrieved from: http://www.opensource.org/licenses/bsd-license.php 9 | %%%------------------------------------------------------------------- 10 | 11 | 12 | -module(emetric_hooks). 13 | 14 | -behaviour(gen_server). 15 | -behaviour(emetric_loadable). 16 | 17 | -include("emetric.hrl"). 18 | %% API 19 | -export([start_link/0, 20 | deps/0, 21 | sup/0, 22 | add/3, 23 | delete/3, 24 | run/2, 25 | run_fold/3 26 | ]). 27 | 28 | %% gen_server callbacks 29 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 30 | terminate/2, code_change/3]). 31 | 32 | -define(SERVER, ?MODULE). 33 | 34 | -record(state, {hooks=[] 35 | }). 36 | 37 | %%%=================================================================== 38 | %%% API 39 | %%%=================================================================== 40 | deps() -> []. 41 | sup() -> ?CHILD(?MODULE,worker). 42 | 43 | 44 | %%-------------------------------------------------------------------- 45 | %% @doc Adds a function to the hook. Currently on gather_hooks and 46 | %% scatter_hooks are supported. 47 | %% 48 | %% @spec (Hook::atom(), Function::atom(), Seq::integer()) -> ok 49 | %% @end 50 | %% -------------------------------------------------------------------- 51 | add(Hook,Function,Seq) when is_function(Function) -> 52 | gen_server:call(emetric_hooks, {add, Hook, Function,Seq}). 53 | 54 | 55 | delete(Hook,Function,Seq) when is_function(Function) -> 56 | gen_server:call(emetric_hooks, {delete, Hook,Function,Seq}). 57 | run(Hook,Args) -> 58 | gen_server:call(emetric_hooks, {run,Hook,Args}). 59 | run_fold(Hook, Val, Args) -> 60 | case catch gen_server:call(emetric_hooks, {run_fold, Hook, Val, Args}) of 61 | {'EXIT', _Reason} -> timeout; 62 | Ticks -> Ticks 63 | end. 64 | 65 | 66 | 67 | %%-------------------------------------------------------------------- 68 | %% @doc 69 | %% Starts the server 70 | %% 71 | %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} 72 | %% @end 73 | %%-------------------------------------------------------------------- 74 | start_link() -> 75 | gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). 76 | 77 | %%%=================================================================== 78 | %%% gen_server callbacks 79 | %%%=================================================================== 80 | 81 | %%-------------------------------------------------------------------- 82 | %% @private 83 | %% @doc 84 | %% Initializes the server 85 | %% 86 | %% @spec init(Args) -> {ok, State} | 87 | %% {ok, State, Timeout} | 88 | %% ignore | 89 | %% {stop, Reason} 90 | %% @end 91 | %%-------------------------------------------------------------------- 92 | init([]) -> 93 | {ok, #state{}}. 94 | 95 | %%-------------------------------------------------------------------- 96 | %% @private 97 | %% @doc 98 | %% Handling call messages 99 | %% 100 | %% @spec handle_call(Request, From, State) -> 101 | %% {reply, Reply, State} | 102 | %% {reply, Reply, State, Timeout} | 103 | %% {noreply, State} | 104 | %% {noreply, State, Timeout} | 105 | %% {stop, Reason, Reply, State} | 106 | %% {stop, Reason, State} 107 | %% @end 108 | %%-------------------------------------------------------------------- 109 | handle_call({add, Hook, Function,Seq}, _From, State) -> 110 | NewHooks = lists:append(State#state.hooks,[{Hook,Seq, Function}]), 111 | {reply, ok, State#state{hooks=NewHooks}}; 112 | 113 | handle_call({delete, Hook, Function,Seq}, _From, State) -> 114 | NewHooks = lists:filter(fun({H,S,F}) -> 115 | case {H,S,F} of 116 | {Hook, Seq,Function} -> false; 117 | _ -> true 118 | end 119 | end,State#state.hooks), 120 | {reply,ok, State#state{hooks=NewHooks}}; 121 | 122 | handle_call({run,Hook,Args}, _From, State) -> 123 | lists:foreach(fun(H) -> 124 | case H of 125 | %% match on the Hook called and run those 126 | {Hook,_Seq,Function} -> 127 | Function(Args); 128 | _ -> ok 129 | end 130 | end, State#state.hooks), 131 | {reply,ok,State}; 132 | handle_call({run_fold,Hook,Val,Args}, _From, State) -> 133 | Reply = run_fold(State#state.hooks, Hook, Val,Args), 134 | {reply, Reply, State}; 135 | 136 | handle_call(_Request, _From, State) -> 137 | Reply = ok, 138 | {reply, Reply, State}. 139 | 140 | %%-------------------------------------------------------------------- 141 | %% @private 142 | %% @doc 143 | %% Handling cast messages 144 | %% 145 | %% @spec handle_cast(Msg, State) -> {noreply, State} | 146 | %% {noreply, State, Timeout} | 147 | %% {stop, Reason, State} 148 | %% @end 149 | %%-------------------------------------------------------------------- 150 | handle_cast(_Msg, State) -> 151 | {noreply, State}. 152 | 153 | %%-------------------------------------------------------------------- 154 | %% @private 155 | %% @doc 156 | %% Handling all non call/cast messages 157 | %% 158 | %% @spec handle_info(Info, State) -> {noreply, State} | 159 | %% {noreply, State, Timeout} | 160 | %% {stop, Reason, State} 161 | %% @end 162 | %%-------------------------------------------------------------------- 163 | handle_info(_Info, State) -> 164 | {noreply, State}. 165 | 166 | %%-------------------------------------------------------------------- 167 | %% @private 168 | %% @doc 169 | %% This function is called by a gen_server when it is about to 170 | %% terminate. It should be the opposite of Module:init/1 and do any 171 | %% necessary cleaning up. When it returns, the gen_server terminates 172 | %% with Reason. The return value is ignored. 173 | %% 174 | %% @spec terminate(Reason, State) -> void() 175 | %% @end 176 | %%-------------------------------------------------------------------- 177 | terminate(_Reason, _State) -> 178 | ok. 179 | 180 | %%-------------------------------------------------------------------- 181 | %% @private 182 | %% @doc 183 | %% Convert process state when code is changed 184 | %% 185 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} 186 | %% @end 187 | %%-------------------------------------------------------------------- 188 | code_change(_OldVsn, State, _Extra) -> 189 | {ok, State}. 190 | 191 | %%%=================================================================== 192 | %%% Internal functions 193 | %%%=================================================================== 194 | 195 | run_fold([],_Hook, Val, _Args) -> Val; 196 | run_fold([H|Rest],Hook, Val, Args) -> 197 | NewVal = case H of 198 | {Hook, _Seq, Function} -> 199 | case catch Function(Args,Val) of 200 | {'EXIT', _Reason} -> []; 201 | Other -> Other 202 | end; 203 | _ -> Val 204 | end, 205 | case NewVal of 206 | stop -> 207 | stopped; 208 | {stop,StopVal} -> 209 | StopVal; 210 | _StopVal -> 211 | run_fold(Rest,Hook,NewVal, Args) 212 | end. 213 | 214 | 215 | 216 | -------------------------------------------------------------------------------- /src/emetric_loadable.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Justin Kirby 3 | %%% @copyright (C) 2011 Justin Kirby 4 | %%% @end 5 | %%% 6 | %%% This source file is subject to the New BSD License. You should have received 7 | %%% a copy of the New BSD license with this software. If not, it can be 8 | %%% retrieved from: http://www.opensource.org/licenses/bsd-license.php 9 | %%%------------------------------------------------------------------- 10 | 11 | 12 | -module(emetric_loadable). 13 | 14 | -export([behaviour_info/1]). 15 | 16 | behaviour_info(callbacks) -> 17 | [{deps,0}, 18 | {sup,0} 19 | ]; 20 | behaviour_info(_Other) -> 21 | undefined. 22 | -------------------------------------------------------------------------------- /src/emetric_log_file.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Justin Kirby 3 | %%% @copyright (C) 2011 Justin Kirby 4 | %%% @end 5 | %%% 6 | %%% This source file is subject to the New BSD License. You should have received 7 | %%% a copy of the New BSD license with this software. If not, it can be 8 | %%% retrieved from: http://www.opensource.org/licenses/bsd-license.php 9 | %%%------------------------------------------------------------------- 10 | 11 | 12 | -module(emetric_log_file). 13 | 14 | -behaviour(gen_server). 15 | -behaviour(emetric_loadable). 16 | 17 | -include("emetric.hrl"). 18 | %% API 19 | -export([start_link/0, 20 | deps/0, 21 | sup/0, 22 | tick/1 23 | ]). 24 | 25 | %% gen_server callbacks 26 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 27 | terminate/2, code_change/3]). 28 | 29 | -define(SERVER, ?MODULE). 30 | 31 | -record(state, {file = 0, 32 | header=false, %% whether we have recorded the header to the file 33 | filter=emetric_filter_csv 34 | }). 35 | 36 | %%%=================================================================== 37 | %%% API 38 | %%%=================================================================== 39 | deps() -> [emetric_hooks]. 40 | sup() -> ?CHILD(?MODULE,worker). 41 | tick(Acc) -> 42 | gen_server:cast(?SERVER, {tick,Acc}). 43 | %%-------------------------------------------------------------------- 44 | %% @doc 45 | %% Starts the server 46 | %% 47 | %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} 48 | %% @end 49 | %%-------------------------------------------------------------------- 50 | start_link() -> 51 | gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). 52 | 53 | %%%=================================================================== 54 | %%% gen_server callbacks 55 | %%%=================================================================== 56 | 57 | %%-------------------------------------------------------------------- 58 | %% @private 59 | %% @doc 60 | %% Initializes the server 61 | %% 62 | %% @spec init(Args) -> {ok, State} | 63 | %% {ok, State, Timeout} | 64 | %% ignore | 65 | %% {stop, Reason} 66 | %% @end 67 | %%-------------------------------------------------------------------- 68 | init([]) -> 69 | emetric_hooks:add(scatter_hooks, fun(A) -> emetric_log_file:tick(A) end,1), 70 | State = #state{}, 71 | 72 | Now = calendar:now_to_universal_time(erlang:now()), 73 | Mod = State#state.filter, 74 | Name = lists:flatten(io_lib:format("/tmp/emetric_~s_~s.~s", 75 | [atom_to_list(node()), 76 | emetric_util:datetime_stamp(Now), 77 | Mod:type()])), 78 | 79 | {ok, FD } = file:open(Name,[write]), 80 | {ok, State#state{file = FD}}. 81 | 82 | %%-------------------------------------------------------------------- 83 | %% @private 84 | %% @doc 85 | %% Handling call messages 86 | %% 87 | %% @spec handle_call(Request, From, State) -> 88 | %% {reply, Reply, State} | 89 | %% {reply, Reply, State, Timeout} | 90 | %% {noreply, State} | 91 | %% {noreply, State, Timeout} | 92 | %% {stop, Reason, Reply, State} | 93 | %% {stop, Reason, State} 94 | %% @end 95 | %%-------------------------------------------------------------------- 96 | handle_call(_Request, _From, State) -> 97 | Reply = ok, 98 | {reply, Reply, State}. 99 | 100 | %%-------------------------------------------------------------------- 101 | %% @private 102 | %% @doc 103 | %% Handling cast messages 104 | %% 105 | %% @spec handle_cast(Msg, State) -> {noreply, State} | 106 | %% {noreply, State, Timeout} | 107 | %% {stop, Reason, State} 108 | %% @end 109 | %%-------------------------------------------------------------------- 110 | handle_cast({tick,Acc}, State) -> 111 | {Lines,NewState} = filter_tick(Acc,State), 112 | io:format(NewState#state.file,Lines,[]), 113 | {noreply, NewState}; 114 | handle_cast(_Msg, State) -> 115 | {noreply, State}. 116 | 117 | %%-------------------------------------------------------------------- 118 | %% @private 119 | %% @doc 120 | %% Handling all non call/cast messages 121 | %% 122 | %% @spec handle_info(Info, State) -> {noreply, State} | 123 | %% {noreply, State, Timeout} | 124 | %% {stop, Reason, State} 125 | %% @end 126 | %%-------------------------------------------------------------------- 127 | handle_info(_Info, State) -> 128 | {noreply, State}. 129 | 130 | %%-------------------------------------------------------------------- 131 | %% @private 132 | %% @doc 133 | %% This function is called by a gen_server when it is about to 134 | %% terminate. It should be the opposite of Module:init/1 and do any 135 | %% necessary cleaning up. When it returns, the gen_server terminates 136 | %% with Reason. The return value is ignored. 137 | %% 138 | %% @spec terminate(Reason, State) -> void() 139 | %% @end 140 | %%-------------------------------------------------------------------- 141 | terminate(_Reason, _State) -> 142 | ok. 143 | 144 | %%-------------------------------------------------------------------- 145 | %% @private 146 | %% @doc 147 | %% Convert process state when code is changed 148 | %% 149 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} 150 | %% @end 151 | %%-------------------------------------------------------------------- 152 | code_change(_OldVsn, State, _Extra) -> 153 | {ok, State}. 154 | 155 | %%%=================================================================== 156 | %%% Internal functions 157 | %%%=================================================================== 158 | filter_tick(Acc,State) -> 159 | Mod = State#state.filter, 160 | {Header,NewState} = case State#state.header of 161 | false -> 162 | Head = Mod:header(Acc), 163 | H = lists:flatten(io_lib:format("~s~n",[Head])), 164 | NS = State#state{header = true}, 165 | {H,NS}; 166 | true -> 167 | {"",State} 168 | end, 169 | Row = Mod:row(Acc), 170 | {lists:flatten(io_lib:format("~s~s~n",[Header,Row])), 171 | NewState}. 172 | 173 | -------------------------------------------------------------------------------- /src/emetric_stats_ejd.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Justin Kirby 3 | %%% @copyright (C) 2011 Justin Kirby 4 | %%% @end 5 | %%% 6 | %%% This source file is subject to the New BSD License. You should have received 7 | %%% a copy of the New BSD license with this software. If not, it can be 8 | %%% retrieved from: http://www.opensource.org/licenses/bsd-license.php 9 | %%%------------------------------------------------------------------- 10 | 11 | 12 | -module(emetric_stats_ejd). 13 | 14 | -behaviour(gen_server). 15 | -behaviour(emetric_loadable). 16 | 17 | -include("emetric.hrl"). 18 | %% API 19 | -export([start_link/0, 20 | deps/0, 21 | sup/0, 22 | tick/2 23 | ]). 24 | 25 | %% gen_server callbacks 26 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 27 | terminate/2, code_change/3]). 28 | 29 | -define(SERVER, ?MODULE). 30 | 31 | -record(state, {}). 32 | 33 | %%%=================================================================== 34 | %%% API 35 | %%%=================================================================== 36 | deps() -> [emetric_hooks]. 37 | sup() -> ?CHILD(?MODULE,worker). 38 | 39 | tick(test,[]) -> 40 | on_tick(0,[],#state{}); 41 | tick(Tick,Acc) -> 42 | gen_server:call(?SERVER, {tick,Tick,Acc}). 43 | 44 | 45 | 46 | 47 | 48 | %%-------------------------------------------------------------------- 49 | %% @doc 50 | %% Starts the server 51 | %% 52 | %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} 53 | %% @end 54 | %%-------------------------------------------------------------------- 55 | start_link() -> 56 | gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). 57 | 58 | %%%=================================================================== 59 | %%% gen_server callbacks 60 | %%%=================================================================== 61 | 62 | %%-------------------------------------------------------------------- 63 | %% @private 64 | %% @doc 65 | %% Initializes the server 66 | %% 67 | %% @spec init(Args) -> {ok, State} | 68 | %% {ok, State, Timeout} | 69 | %% ignore | 70 | %% {stop, Reason} 71 | %% @end 72 | %%-------------------------------------------------------------------- 73 | init([]) -> 74 | emetric_hooks:add(gather_hooks, fun(T,A) -> emetric_stats_ejd:tick(T,A) end,2), 75 | {ok, #state{}}. 76 | 77 | %%-------------------------------------------------------------------- 78 | %% @private 79 | %% @doc 80 | %% Handling call messages 81 | %% 82 | %% @spec handle_call(Request, From, State) -> 83 | %% {reply, Reply, State} | 84 | %% {reply, Reply, State, Timeout} | 85 | %% {noreply, State} | 86 | %% {noreply, State, Timeout} | 87 | %% {stop, Reason, Reply, State} | 88 | %% {stop, Reason, State} 89 | %% @end 90 | %%-------------------------------------------------------------------- 91 | handle_call({tick, Tick, Acc}, _From, State) -> 92 | {reply,on_tick(Tick,Acc,State),State}; 93 | handle_call(_Request, _From, State) -> 94 | Reply = ok, 95 | {reply, Reply, State}. 96 | 97 | %%-------------------------------------------------------------------- 98 | %% @private 99 | %% @doc 100 | %% Handling cast messages 101 | %% 102 | %% @spec handle_cast(Msg, State) -> {noreply, State} | 103 | %% {noreply, State, Timeout} | 104 | %% {stop, Reason, State} 105 | %% @end 106 | %%-------------------------------------------------------------------- 107 | handle_cast(_Msg, State) -> 108 | {noreply, State}. 109 | 110 | %%-------------------------------------------------------------------- 111 | %% @private 112 | %% @doc 113 | %% Handling all non call/cast messages 114 | %% 115 | %% @spec handle_info(Info, State) -> {noreply, State} | 116 | %% {noreply, State, Timeout} | 117 | %% {stop, Reason, State} 118 | %% @end 119 | %%-------------------------------------------------------------------- 120 | handle_info(_Info, State) -> 121 | {noreply, State}. 122 | 123 | %%-------------------------------------------------------------------- 124 | %% @private 125 | %% @doc 126 | %% This function is called by a gen_server when it is about to 127 | %% terminate. It should be the opposite of Module:init/1 and do any 128 | %% necessary cleaning up. When it returns, the gen_server terminates 129 | %% with Reason. The return value is ignored. 130 | %% 131 | %% @spec terminate(Reason, State) -> void() 132 | %% @end 133 | %%-------------------------------------------------------------------- 134 | terminate(_Reason, _State) -> 135 | ok. 136 | 137 | %%-------------------------------------------------------------------- 138 | %% @private 139 | %% @doc 140 | %% Convert process state when code is changed 141 | %% 142 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} 143 | %% @end 144 | %%-------------------------------------------------------------------- 145 | code_change(_OldVsn, State, _Extra) -> 146 | {ok, State}. 147 | 148 | %%%=================================================================== 149 | %%% Internal functions 150 | %%%=================================================================== 151 | 152 | on_tick(Tick,Acc,State) -> 153 | %% loop over all the ejabberd hosts and provide: 154 | %% [{"example.com",[{stat,val},...]},...] 155 | HostStats = [{"global",constants(State) ++ global_stats()}] ++ 156 | lists:map(fun(Host) -> 157 | {Host, stats(Host)} 158 | end,ejabberd_config:get_global_option(hosts)), 159 | 160 | Data = [{ejd, 161 | [{tick,Tick}, 162 | {hosts, HostStats}] 163 | }], 164 | Acc++Data. 165 | 166 | constants(_State) -> 167 | [{now,now()}]. 168 | 169 | global_stats() -> 170 | Sessions = length(ejabberd_sm:dirty_get_my_sessions_list()), 171 | [{sessions,Sessions}]. 172 | 173 | stats(Host) -> 174 | Users = ejabberd_auth:get_vh_registered_users_number(Host), 175 | Online = length(ejabberd_sm:get_vh_session_list(Host)), 176 | [{users_total, Users}, 177 | {users_online, Online}]. 178 | 179 | -------------------------------------------------------------------------------- /src/emetric_stats_mnesia.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Justin Kirby 3 | %%% @copyright (C) 2011 Justin Kirby 4 | %%% @end 5 | %%% 6 | %%% This source file is subject to the New BSD License. You should have received 7 | %%% a copy of the New BSD license with this software. If not, it can be 8 | %%% retrieved from: http://www.opensource.org/licenses/bsd-license.php 9 | %%%------------------------------------------------------------------- 10 | 11 | 12 | -module(emetric_stats_mnesia). 13 | 14 | -behaviour(gen_server). 15 | -behaviour(emetric_loadable). 16 | 17 | -include("emetric.hrl"). 18 | %% API 19 | -export([start_link/0, 20 | deps/0, 21 | sup/0, 22 | tick/2 23 | ]). 24 | 25 | %% gen_server callbacks 26 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 27 | terminate/2, code_change/3]). 28 | 29 | -define(SERVER, ?MODULE). 30 | 31 | -record(state, {}). 32 | 33 | %%%=================================================================== 34 | %%% API 35 | %%%=================================================================== 36 | deps() -> [emetric_hooks]. 37 | sup() -> ?CHILD(?MODULE,worker). 38 | 39 | tick(test,[]) -> 40 | on_tick(0,[],#state{}); 41 | tick(Tick,Acc) -> 42 | gen_server:call(?SERVER, {tick,Tick,Acc}). 43 | 44 | 45 | %%-------------------------------------------------------------------- 46 | %% @doc 47 | %% Starts the server 48 | %% 49 | %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} 50 | %% @end 51 | %%-------------------------------------------------------------------- 52 | start_link() -> 53 | gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). 54 | 55 | %%%=================================================================== 56 | %%% gen_server callbacks 57 | %%%=================================================================== 58 | 59 | %%-------------------------------------------------------------------- 60 | %% @private 61 | %% @doc 62 | %% Initializes the server 63 | %% 64 | %% @spec init(Args) -> {ok, State} | 65 | %% {ok, State, Timeout} | 66 | %% ignore | 67 | %% {stop, Reason} 68 | %% @end 69 | %%-------------------------------------------------------------------- 70 | init([]) -> 71 | emetric_hooks:add(gather_hooks, fun(T,A) -> emetric_stats_mnesia:tick(T,A) end,2), 72 | {ok, #state{}}. 73 | 74 | %%-------------------------------------------------------------------- 75 | %% @private 76 | %% @doc 77 | %% Handling call messages 78 | %% 79 | %% @spec handle_call(Request, From, State) -> 80 | %% {reply, Reply, State} | 81 | %% {reply, Reply, State, Timeout} | 82 | %% {noreply, State} | 83 | %% {noreply, State, Timeout} | 84 | %% {stop, Reason, Reply, State} | 85 | %% {stop, Reason, State} 86 | %% @end 87 | %%-------------------------------------------------------------------- 88 | handle_call({tick, Tick, Acc}, _From, State) -> 89 | {reply,on_tick(Tick,Acc,State),State}; 90 | handle_call(_Request, _From, State) -> 91 | Reply = ok, 92 | {reply, Reply, State}. 93 | 94 | %%-------------------------------------------------------------------- 95 | %% @private 96 | %% @doc 97 | %% Handling cast messages 98 | %% 99 | %% @spec handle_cast(Msg, State) -> {noreply, State} | 100 | %% {noreply, State, Timeout} | 101 | %% {stop, Reason, State} 102 | %% @end 103 | %%-------------------------------------------------------------------- 104 | handle_cast(_Msg, State) -> 105 | {noreply, State}. 106 | 107 | %%-------------------------------------------------------------------- 108 | %% @private 109 | %% @doc 110 | %% Handling all non call/cast messages 111 | %% 112 | %% @spec handle_info(Info, State) -> {noreply, State} | 113 | %% {noreply, State, Timeout} | 114 | %% {stop, Reason, State} 115 | %% @end 116 | %%-------------------------------------------------------------------- 117 | handle_info(_Info, State) -> 118 | {noreply, State}. 119 | 120 | %%-------------------------------------------------------------------- 121 | %% @private 122 | %% @doc 123 | %% This function is called by a gen_server when it is about to 124 | %% terminate. It should be the opposite of Module:init/1 and do any 125 | %% necessary cleaning up. When it returns, the gen_server terminates 126 | %% with Reason. The return value is ignored. 127 | %% 128 | %% @spec terminate(Reason, State) -> void() 129 | %% @end 130 | %%-------------------------------------------------------------------- 131 | terminate(_Reason, _State) -> 132 | ok. 133 | 134 | %%-------------------------------------------------------------------- 135 | %% @private 136 | %% @doc 137 | %% Convert process state when code is changed 138 | %% 139 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} 140 | %% @end 141 | %%-------------------------------------------------------------------- 142 | code_change(_OldVsn, State, _Extra) -> 143 | {ok, State}. 144 | 145 | %%%=================================================================== 146 | %%% Internal functions 147 | %%%=================================================================== 148 | 149 | on_tick(_Tick,Acc,_State) -> 150 | SystemMetrics = [{held_locks,cnt},%% need to do a len() 151 | {lock_queue,cnt},%% need to do a len() 152 | {subscribers,cnt},%% need to do a len() 153 | {tables,cnt},%% len() 154 | {transactions,cnt},%% len() 155 | transaction_failures, 156 | transaction_commits, 157 | transaction_restarts, 158 | transaction_log_writes], 159 | TableMetrics = [{checkpoints,cnt}, 160 | {subscribers,cnt}, 161 | memory, 162 | size], 163 | 164 | System = lists:map(fun({K,cnt}) -> 165 | {K,length(mnesia:system_info(K))}; 166 | (K) -> 167 | {K,mnesia:system_info(K)} 168 | end,SystemMetrics), 169 | 170 | 171 | Tables = mnesia:system_info(tables), 172 | 173 | TableData = lists:map(fun(Table) -> 174 | Metrics = lists:map(fun({K,cnt}) -> 175 | {K, length(mnesia:table_info(Table,K))}; 176 | (K) -> 177 | {K,mnesia:table_info(Table,K)} 178 | end,TableMetrics), 179 | {Table,Metrics} 180 | end,Tables), 181 | [{mnesia, [{system, System}, 182 | {table, TableData}]}|Acc]. 183 | 184 | 185 | -------------------------------------------------------------------------------- /src/emetric_stats_sys.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Justin Kirby 3 | %%% @copyright (C) 2011 Justin Kirby 4 | %%% @end 5 | %%% 6 | %%% This source file is subject to the New BSD License. You should have received 7 | %%% a copy of the New BSD license with this software. If not, it can be 8 | %%% retrieved from: http://www.opensource.org/licenses/bsd-license.php 9 | %%%------------------------------------------------------------------- 10 | 11 | 12 | -module(emetric_stats_sys). 13 | 14 | -behaviour(gen_server). 15 | 16 | -include("emetric.hrl"). 17 | %% API 18 | -export([start_link/0, 19 | deps/0, 20 | sup/0, 21 | tick/2 22 | ]). 23 | 24 | %% gen_server callbacks 25 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 26 | terminate/2, code_change/3]). 27 | 28 | -define(SERVER, ?MODULE). 29 | 30 | %% I did not know you call a fun in record init! cool 31 | -record(state, {strategy=strategy(), 32 | node = node(), 33 | total_ram=0, 34 | cores=1, 35 | cache=[], 36 | now=now() 37 | }). 38 | 39 | 40 | 41 | 42 | 43 | 44 | %%%=================================================================== 45 | %%% API 46 | %%%===================================================================q 47 | 48 | deps() -> [emetric_hooks]. 49 | sup() -> ?CHILD(?MODULE,worker). 50 | tick(test,[]) -> 51 | on_tick(0,[],init_state(#state{})); 52 | tick(Tick,Acc) -> 53 | gen_server:call(?SERVER, {tick, Tick, Acc}). 54 | 55 | 56 | %%-------------------------------------------------------------------- 57 | %% @doc 58 | %% Starts the server 59 | %% 60 | %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} 61 | %% @end 62 | %%-------------------------------------------------------------------- 63 | start_link() -> 64 | gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). 65 | 66 | %%%=================================================================== 67 | %%% gen_server callbacks 68 | %%%=================================================================== 69 | 70 | %%-------------------------------------------------------------------- 71 | %% @private 72 | %% @doc 73 | %% Initializes the server 74 | %% 75 | %% @spec init(Args) -> {ok, State} | 76 | %% {ok, State, Timeout} | 77 | %% ignore | 78 | %% {stop, Reason} 79 | %% @end 80 | %%-------------------------------------------------------------------- 81 | init([]) -> 82 | emetric_hooks:add(gather_hooks, fun(T,A) -> emetric_stats_sys:tick(T,A) end,1), 83 | {ok, init_state(#state{})}. 84 | 85 | %%-------------------------------------------------------------------- 86 | %% @private 87 | %% @doc 88 | %% Handling call messages 89 | %% 90 | %% @spec handle_call(Request, From, State) -> 91 | %% {reply, Reply, State} | 92 | %% {reply, Reply, State, Timeout} | 93 | %% {noreply, State} | 94 | %% {noreply, State, Timeout} | 95 | %% {stop, Reason, Reply, State} | 96 | %% {stop, Reason, State} 97 | %% @end 98 | %%-------------------------------------------------------------------- 99 | handle_call({tick, Tick,Acc}, _From, State) -> 100 | {reply, on_tick(Tick,Acc,State),State}; 101 | handle_call(_Request, _From, State) -> 102 | Reply = ok, 103 | {reply, Reply, State}. 104 | 105 | %%-------------------------------------------------------------------- 106 | %% @private 107 | %% @doc 108 | %% Handling cast messages 109 | %% 110 | %% @spec handle_cast(Msg, State) -> {noreply, State} | 111 | %% {noreply, State, Timeout} | 112 | %% {stop, Reason, State} 113 | %% @end 114 | %%-------------------------------------------------------------------- 115 | handle_cast(_Msg, State) -> 116 | {noreply, State}. 117 | 118 | %%-------------------------------------------------------------------- 119 | %% @private 120 | %% @doc 121 | %% Handling all non call/cast messages 122 | %% 123 | %% @spec handle_info(Info, State) -> {noreply, State} | 124 | %% {noreply, State, Timeout} | 125 | %% {stop, Reason, State} 126 | %% @end 127 | %%-------------------------------------------------------------------- 128 | handle_info(_Info, State) -> 129 | {noreply, State}. 130 | 131 | %%-------------------------------------------------------------------- 132 | %% @private 133 | %% @doc 134 | %% This function is called by a gen_server when it is about to 135 | %% terminate. It should be the opposite of Module:init/1 and do any 136 | %% necessary cleaning up. When it returns, the gen_server terminates 137 | %% with Reason. The return value is ignored. 138 | %% 139 | %% @spec terminate(Reason, State) -> void() 140 | %% @end 141 | %%-------------------------------------------------------------------- 142 | terminate(_Reason, _State) -> 143 | ok. 144 | 145 | %%-------------------------------------------------------------------- 146 | %% @private 147 | %% @doc 148 | %% Convert process state when code is changed 149 | %% 150 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} 151 | %% @end 152 | %%-------------------------------------------------------------------- 153 | code_change(_OldVsn, State, _Extra) -> 154 | {ok, State}. 155 | 156 | %%%=================================================================== 157 | %%% Internal functions 158 | %%%=================================================================== 159 | 160 | 161 | %% tag [unit] source 162 | %% node [atom()] erlang:node() 163 | %% now [now()] erlang:now() 164 | %% procs [count] erlang:system_info(process_count) 165 | %% context_switches [count/s] erlang:statistics(context_switches) 166 | %% gcs [count/s] erlang:statistics(garbage_collection) 167 | %% gc_reclaimed [byte/s] erlang:statistics(garbage_collection) 168 | %% io_in [byte/s] erlang:statistics(io) 169 | %% io_out [byte/s] erlang:statistics(io) 170 | %% reductions [count/s] erlang:statistics(reductions) 171 | %% reducts_since [count/s] erlang:statistics(reductions) 172 | %% run_queue [count] erlang:statistics(run_queue) 173 | %% total [byte] erlang:memory() 174 | %% processes [byte] erlang:memory() 175 | %% processes_used [byte] erlang:memory() 176 | %% system [byte] erlang:memory() 177 | %% atom [byte] erlang:memory() 178 | %% atom_used [byte] erlang:memory() 179 | %% binary [byte] erlang:memory() 180 | %% code [byte] erlang:memory() 181 | %% ets [byte] erlang:memory() 182 | %% user [frac] /proc/stat 183 | %% nice [frac] /proc/stat 184 | %% kernel [frac] /proc/stat 185 | %% idle [frac] /proc/stat 186 | %% iowait [frac] /proc/stat 187 | %% ctxt [frac] /proc/stat 188 | %% beam_user, [frac] /proc/self/stat 189 | %% beam_kernel, [frac] /proc/self/stat 190 | %% beam_vss [byte] /proc/self/stat 191 | %% beam_rss [pages] /proc/self/stat 192 | %% beam_minflt [count/s] /proc/self/stat 193 | %% beam_majflt [count/s] /proc/self/stat 194 | %% total_ram [byte] /proc/meminfo 195 | on_tick(Tick,Acc,State) -> 196 | Data = [{tick,Tick}]++constants(State)++stats()++os_info(State#state.strategy), 197 | Acc++[{sys,Data}]. 198 | 199 | constants(#state{node=Node,total_ram=Total_ram,cores=Cores}) -> 200 | [{node, Node},{total_ram,Total_ram},{cores,Cores}]. 201 | 202 | 203 | stats() -> 204 | Procs = erlang:system_info(process_count), 205 | {Ctx, 0} = erlang:statistics(context_switches), 206 | {GCs,GCwords,0} = erlang:statistics(garbage_collection), 207 | {{input,IoIn},{output,IoOut}} = erlang:statistics(io), 208 | {Reducts,ReductSince} = erlang:statistics(reductions), 209 | RunQ = erlang:statistics(run_queue), 210 | 211 | [{now,now()}, 212 | {procs,Procs}, 213 | {context_switches,Ctx}, 214 | {gcs, GCs}, 215 | {gc_reclaimed,GCwords}, 216 | {io_in,IoIn}, 217 | {io_out,IoOut}, 218 | {reductions,Reducts}, 219 | {reducts_since,ReductSince}, 220 | {run_queue,RunQ} | 221 | erlang:memory()]. 222 | 223 | 224 | %% OS info 225 | %% only the 'linux' (i.e. linux 2.6 or higher) strategy implemented 226 | -record(fds,{proc_stat,proc_self_stat,proc_meminfo,proc_net}). 227 | 228 | 229 | os_info({linux,#fds{proc_stat=FDs,proc_self_stat=FDss,proc_meminfo=Mi, proc_net=Net}}) -> 230 | proc_stat(FDs)++proc_self_stat(FDss)++proc_meminfo(Mi)++proc_net(Net); 231 | os_info(_) -> 232 | []. 233 | 234 | proc_stat(FDs) -> 235 | %%user nice kernel idle iowait irq softirq steal 236 | {ok,Str} = file:pread(FDs,0,200), 237 | case string:tokens(Str," \n") of 238 | ["cpu",User,Nice,Kernel,Idle,Iowait|_] -> ok; 239 | _ -> User=Nice=Kernel=Idle=Iowait=0 240 | end, 241 | lists:zip([user,nice,kernel,idle,iowait], 242 | [to_sec(J) || J <- [User,Nice,Kernel,Idle,Iowait]]). 243 | 244 | proc_self_stat(FDss) -> 245 | %%% pid,comm,state,ppid,pgrp,session,tty_nr,tpgid,flags, 246 | %%% minflt,cminflt,majflt,cmajflt,utime,stime,cutime,cstime, 247 | %%% priority,nice,num_threads,itrealvalue,starttime,vsize,rss 248 | {ok,Str} = file:pread(FDss,0,200), 249 | case string:tokens(Str," ") of 250 | [_,_,_,_,_,_,_,_,_, 251 | Minflt,_,Majflt,_,Utime,Stime,_,_, 252 | _,_,_,_,_,Vsize,Rss|_] -> ok; 253 | _ -> Minflt=Majflt=Utime=Stime=Vsize=Rss=0 254 | end, 255 | lists:zip([beam_user,beam_kernel,beam_vss,beam_rss,beam_minflt,beam_majflt], 256 | [to_sec(Utime),to_sec(Stime),to_int(Vsize), 257 | to_int(Rss), %% in pages... 258 | to_int(Minflt),to_int(Majflt)]). 259 | 260 | proc_meminfo(Fd) -> 261 | {ok, Str} = file:pread(Fd,0,2048), 262 | mem_info(string:tokens(Str, " \n")). 263 | 264 | proc_net(Fd) -> 265 | {ok,Str} = file:pread(Fd,0,1024), 266 | net_info(string:tokens(Str,"\n")). 267 | 268 | to_sec(J) -> 269 | to_int(J)/100. %should use a better transform jiffies->secs 270 | 271 | to_int(J) -> list_to_integer(J). 272 | 273 | cores({linux,#fds{proc_stat=Proc_stat}}) -> 274 | {ok,Str} = file:pread(Proc_stat,0,1000), 275 | Toks = string:tokens(Str,"\n"), 276 | case length(lists:takewhile(fun(S)->lists:prefix("cpu",S) end,Toks)) of 277 | 1 -> 1; 278 | M -> M-1 279 | end; 280 | cores(_) -> 281 | 1. 282 | 283 | %% only doing linux now 284 | strategy() -> 285 | case {os:type(),os:version()} of 286 | {{unix,linux},{2,_,_}} -> {linux, init_linux()}; 287 | _ -> {none,[]} 288 | end. 289 | 290 | init_linux() -> 291 | {ok,FDs} = file:open("/proc/stat",[read]), 292 | {ok,FDss} = file:open("/proc/self/stat",[read]), 293 | {ok,Mi} = file:open("/proc/meminfo",[read]), 294 | {ok,Net} = file:open("/proc/net/dev",[read]), 295 | #fds{proc_stat=FDs, proc_self_stat=FDss, proc_meminfo=Mi, proc_net=Net}. 296 | 297 | 298 | 299 | 300 | total_ram() -> 301 | case file:open("/proc/meminfo",[read]) of 302 | {ok,FD} -> 303 | try {ok,Str} = file:pread(FD,0,30), 304 | ["MemTotal:",T,"kB"|_] = string:tokens(Str," \n"), 305 | list_to_integer(T)*1024 306 | catch _:_ -> 0 307 | after file:close(FD) 308 | end; 309 | _ -> 0 310 | end. 311 | 312 | 313 | init_state(State = #state{strategy={linux,_}}) -> 314 | State#state{total_ram = total_ram(), 315 | cores = cores(State#state.strategy)}; 316 | init_state(State) -> 317 | State. 318 | 319 | 320 | %% MemTotal: 8168216 kB 321 | %% MemFree: 3339092 kB 322 | %% Buffers: 11080 kB 323 | %% Cached: 186824 kB 324 | %% SwapCached: 266908 kB 325 | %% Active: 2011000 kB 326 | %% Inactive: 2703476 kB 327 | %% HighTotal: 0 kB 328 | %% HighFree: 0 kB 329 | %% LowTotal: 8168216 kB 330 | %% LowFree: 3339092 kB 331 | %% SwapTotal: 6088624 kB 332 | %% SwapFree: 2576548 kB 333 | %% Dirty: 208 kB 334 | %% Writeback: 0 kB 335 | %% AnonPages: 4267468 kB 336 | %% Mapped: 10648 kB 337 | %% Slab: 51352 kB 338 | %% PageTables: 30640 kB 339 | %% NFS_Unstable: 0 kB 340 | %% Bounce: 0 kB 341 | %% CommitLimit: 10172732 kB 342 | %% Committed_AS: 12786192 kB 343 | %% VmallocTotal: 34359738367 kB 344 | %% VmallocUsed: 266012 kB 345 | %% VmallocChunk: 34359472251 kB 346 | %% HugePages_Total: 0 347 | %% HugePages_Free: 0 348 | %% HugePages_Rsvd: 0 349 | %% Hugepagesize: 2048 kB 350 | mem_info(["MemTotal:",V,"kB"|T]) -> 351 | [{mem_total, list_to_integer(V)*1024}|mem_info(T)]; 352 | mem_info(["MemFree:",V,"kB"|T]) -> 353 | [{mem_free, list_to_integer(V)*1024}|mem_info(T)]; 354 | mem_info(["Buffers:",V,"kB"|T]) -> 355 | [{buffers, list_to_integer(V)*1024}|mem_info(T)]; 356 | mem_info(["Cached:",V,"kB"|T]) -> 357 | [{cached, list_to_integer(V)*1024}|mem_info(T)]; 358 | mem_info(["SwapCached:",V,"kB"|T]) -> 359 | [{swap_cached, list_to_integer(V)*1024}|mem_info(T)]; 360 | mem_info(["Active:",V,"kB"|T]) -> 361 | [{active, list_to_integer(V)*1024}|mem_info(T)]; 362 | mem_info(["Inactive:",V,"kB"|T]) -> 363 | [{inactive, list_to_integer(V)*1024}|mem_info(T)]; 364 | mem_info(["HighTotal:",V,"kB"|T]) -> 365 | [{high_total, list_to_integer(V)*1024}|mem_info(T)]; 366 | mem_info(["HighFree:",V,"kB"|T]) -> 367 | [{high_free, list_to_integer(V)*1024}|mem_info(T)]; 368 | mem_info(["LowTotal:",V,"kB"|T]) -> 369 | [{low_total, list_to_integer(V)*1024}|mem_info(T)]; 370 | mem_info(["LowFree:",V,"kB"|T]) -> 371 | [{low_free, list_to_integer(V)*1024}|mem_info(T)]; 372 | mem_info(["SwapTotal:",V,"kB"|T]) -> 373 | [{swap_total, list_to_integer(V)*1024}|mem_info(T)]; 374 | mem_info(["SwapFree:",V,"kB"|T]) -> 375 | [{swap_free, list_to_integer(V)*1024}|mem_info(T)]; 376 | mem_info(["Dirty:",V,"kB"|T]) -> 377 | [{dirty, list_to_integer(V)*1024}|mem_info(T)]; 378 | mem_info(["Writeback:",V,"kB"|T]) -> 379 | [{write_back, list_to_integer(V)*1024}|mem_info(T)]; 380 | mem_info(["AnonPages:",V,"kB"|T]) -> 381 | [{anon_pages, list_to_integer(V)*1024}|mem_info(T)]; 382 | mem_info(["Mapped:",V,"kB"|T]) -> 383 | [{mapped, list_to_integer(V)*1024}|mem_info(T)]; 384 | mem_info(["Slab:",V,"kB"|T]) -> 385 | [{slab, list_to_integer(V)*1024}|mem_info(T)]; 386 | mem_info(["PageTables:",V,"kB"|T]) -> 387 | [{page_tables, list_to_integer(V)*1024}|mem_info(T)]; 388 | mem_info(["NFS_Unstable:",V,"kB"|T]) -> 389 | [{nfs_unstable, list_to_integer(V)*1024}|mem_info(T)]; 390 | mem_info(["Bounce:",V,"kB"|T]) -> 391 | [{bounce, list_to_integer(V)*1024}|mem_info(T)]; 392 | mem_info(["CommitLimit:",V,"kB"|T]) -> 393 | [{commit_limit, list_to_integer(V)*1024}|mem_info(T)]; 394 | mem_info(["Committed_AS:",V,"kB"|T]) -> 395 | [{committed_as, list_to_integer(V)*1024}|mem_info(T)]; 396 | mem_info(["VmallocTotal:",V,"kB"|T]) -> 397 | [{vmalloc_total, list_to_integer(V)*1024}|mem_info(T)]; 398 | mem_info(["VmallocUsed:",V,"kB"|T]) -> 399 | [{vmalloc_used, list_to_integer(V)*1024}|mem_info(T)]; 400 | mem_info(["VmallocChunk:",V,"kB"|T]) -> 401 | [{vmalloc_chunk, list_to_integer(V)*1024}|mem_info(T)]; 402 | mem_info(["Hugepagesize:",V,"kB"|T]) -> 403 | [{hugepagesize, list_to_integer(V)*1024}|mem_info(T)]; 404 | mem_info([_Unk, _V,"kB"|T]) -> 405 | mem_info(T); 406 | mem_info([_Unk, "0"|T]) -> 407 | mem_info(T); 408 | mem_info([]) -> []. 409 | 410 | 411 | 412 | net_info([_H|T]) -> 413 | %ignore the first line: 414 | net_info(T,cols). 415 | net_info([H|T],cols) -> 416 | [_Face, Receive, Transmit] = string:tokens(H,"|"), 417 | Rcols = lists:map(fun(C) -> "recv_"++C end,string:tokens(Receive," ")), 418 | Tcols = lists:map(fun(C) -> "trans_"++C end, string:tokens(Transmit," ")), 419 | net_info(T, Rcols++Tcols); 420 | net_info([H|T],Cols) -> 421 | [Face, Data] = string:tokens(H,":"), 422 | PlainFace = string:strip(Face), 423 | Fcols = lists:map(fun(C) -> list_to_atom(PlainFace++"_"++C) end,Cols), 424 | DataInt = lists:map(fun(D) -> list_to_integer(D) end, string:tokens(Data, " ")), 425 | lists:zip(Fcols,DataInt)++net_info(T,Cols); 426 | net_info([],_Cols) -> []. 427 | 428 | 429 | -------------------------------------------------------------------------------- /src/emetric_sup.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Justin Kirby 3 | %%% @copyright (C) 2011 Justin Kirby 4 | %%% @end 5 | %%% 6 | %%% This source file is subject to the New BSD License. You should have received 7 | %%% a copy of the New BSD license with this software. If not, it can be 8 | %%% retrieved from: http://www.opensource.org/licenses/bsd-license.php 9 | %%%------------------------------------------------------------------- 10 | 11 | 12 | -module(emetric_sup). 13 | 14 | -behaviour(supervisor). 15 | 16 | %% API 17 | -export([start_link/1]). 18 | 19 | %% Supervisor callbacks 20 | -export([init/1]). 21 | 22 | -include("emetric.hrl"). 23 | %% =================================================================== 24 | %% API functions 25 | %% =================================================================== 26 | 27 | start_link(Children) -> 28 | supervisor:start_link({local, ?MODULE}, ?MODULE, [Children]). 29 | 30 | %% =================================================================== 31 | %% Supervisor callbacks 32 | %% =================================================================== 33 | 34 | init([Children]) -> 35 | {ok, { {one_for_one, 5, 10}, Children} }. 36 | 37 | -------------------------------------------------------------------------------- /src/emetric_ticker.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Justin Kirby 3 | %%% @copyright (C) 2011 Justin Kirby 4 | %%% @end 5 | %%% 6 | %%% This source file is subject to the New BSD License. You should have received 7 | %%% a copy of the New BSD license with this software. If not, it can be 8 | %%% retrieved from: http://www.opensource.org/licenses/bsd-license.php 9 | %%%------------------------------------------------------------------- 10 | 11 | 12 | -module(emetric_ticker). 13 | 14 | -behaviour(gen_server). 15 | -behaviour(emetric_loadable). 16 | 17 | -include("emetric.hrl"). 18 | %% API 19 | -export([start_link/0, 20 | start_link/1, 21 | deps/0, 22 | sup/0, 23 | tick/0, 24 | scatter/1 25 | ]). 26 | 27 | %% gen_server callbacks 28 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 29 | terminate/2, code_change/3]). 30 | 31 | -define(SERVER, ?MODULE). 32 | -define(TICK,2000). 33 | 34 | -record(state, {tick = ?TICK,timer=0,tick_count=0}). 35 | 36 | %%%=================================================================== 37 | %%% API 38 | %%%=================================================================== 39 | deps() -> [emetric_hooks]. 40 | sup() -> ?CHILD(?MODULE,worker). 41 | 42 | tick() -> 43 | gen_server:cast(?MODULE, {tick}). 44 | 45 | scatter(Ticks) -> 46 | gen_server:cast(?MODULE, {scatter, Ticks}). 47 | %% erlang:start_timer(tick_sz(?TICK,0),self(),{tick}). 48 | 49 | %%got this from eper prf.erl:44 50 | %% tick_sz(Tick,Offset) -> 51 | %% {_,Sec,Usec} = now(), 52 | %% Skew = Tick div 4, 53 | %% Tick + Skew-((round(Sec*1000+Usec/1000)-Offset+Skew) rem Tick). 54 | 55 | %%-------------------------------------------------------------------- 56 | %% @doc 57 | %% Starts the server 58 | %% 59 | %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} 60 | %% @end 61 | %%-------------------------------------------------------------------- 62 | start_link() -> 63 | gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). 64 | start_link(Tick) -> 65 | gen_server:start_link({local, ?SERVER}, ?MODULE, [Tick], []). 66 | 67 | 68 | %%%=================================================================== 69 | %%% gen_server callbacks 70 | %%%=================================================================== 71 | 72 | %%-------------------------------------------------------------------- 73 | %% @private 74 | %% @doc 75 | %% Initializes the server 76 | %% 77 | %% @spec init(Args) -> {ok, State} | 78 | %% {ok, State, Timeout} | 79 | %% ignore | 80 | %% {stop, Reason} 81 | %% @end 82 | %%-------------------------------------------------------------------- 83 | init([]) -> 84 | {ok, Tref} = start_timer(?TICK), 85 | {ok, #state{timer=Tref}}; 86 | init([Tick]) -> 87 | {ok, Tref} = start_timer(Tick), 88 | {ok, #state{tick=Tick,timer=Tref}}. 89 | 90 | %%-------------------------------------------------------------------- 91 | %% @private 92 | %% @doc 93 | %% Handling call messages 94 | %% 95 | %% @spec handle_call(Request, From, State) -> 96 | %% {reply, Reply, State} | 97 | %% {reply, Reply, State, Timeout} | 98 | %% {noreply, State} | 99 | %% {noreply, State, Timeout} | 100 | %% {stop, Reason, Reply, State} | 101 | %% {stop, Reason, State} 102 | %% @end 103 | %%-------------------------------------------------------------------- 104 | handle_call(_Request, _From, State) -> 105 | Reply = ok, 106 | {reply, Reply, State}. 107 | 108 | %%-------------------------------------------------------------------- 109 | %% @private 110 | %% @doc 111 | %% Handling cast messages 112 | %% 113 | %% @spec handle_cast(Msg, State) -> {noreply, State} | 114 | %% {noreply, State, Timeout} | 115 | %% {stop, Reason, State} 116 | %% @end 117 | %%-------------------------------------------------------------------- 118 | handle_cast({tick}, State) -> 119 | Cnt = State#state.tick_count, 120 | case emetric_hooks:run_fold(gather_hooks,[],Cnt) of 121 | timeout -> ok; 122 | Ticks -> emetric_ticker:scatter(Ticks) 123 | end, 124 | {noreply,State#state{tick_count = Cnt+1}}; 125 | 126 | handle_cast({scatter,Ticks}, State) -> 127 | emetric_hooks:run(scatter_hooks,Ticks), 128 | {noreply,State}; 129 | 130 | handle_cast(_Msg, State) -> 131 | {noreply, State}. 132 | 133 | %%-------------------------------------------------------------------- 134 | %% @private 135 | %% @doc 136 | %% Handling all non call/cast messages 137 | %% 138 | %% @spec handle_info(Info, State) -> {noreply, State} | 139 | %% {noreply, State, Timeout} | 140 | %% {stop, Reason, State} 141 | %% @end 142 | %%-------------------------------------------------------------------- 143 | handle_info(_Info, State) -> 144 | {noreply, State}. 145 | 146 | %%-------------------------------------------------------------------- 147 | %% @private 148 | %% @doc 149 | %% This function is called by a gen_server when it is about to 150 | %% terminate. It should be the opposite of Module:init/1 and do any 151 | %% necessary cleaning up. When it returns, the gen_server terminates 152 | %% with Reason. The return value is ignored. 153 | %% 154 | %% @spec terminate(Reason, State) -> void() 155 | %% @end 156 | %%-------------------------------------------------------------------- 157 | terminate(_Reason, _State) -> 158 | ok. 159 | 160 | %%-------------------------------------------------------------------- 161 | %% @private 162 | %% @doc 163 | %% Convert process state when code is changed 164 | %% 165 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} 166 | %% @end 167 | %%-------------------------------------------------------------------- 168 | code_change(_OldVsn, State, _Extra) -> 169 | {ok, State}. 170 | 171 | %%%=================================================================== 172 | %%% Internal functions 173 | %%%=================================================================== 174 | start_timer(Tick) -> 175 | timer:apply_interval(Tick,emetric_ticker,tick,[]). 176 | -------------------------------------------------------------------------------- /src/emetric_util.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Justin Kirby 3 | %%% @copyright (C) 2011 Justin Kirby 4 | %%% @end 5 | %%% 6 | %%% This source file is subject to the New BSD License. You should have received 7 | %%% a copy of the New BSD license with this software. If not, it can be 8 | %%% retrieved from: http://www.opensource.org/licenses/bsd-license.php 9 | %%%------------------------------------------------------------------- 10 | 11 | 12 | -module(emetric_util). 13 | 14 | 15 | -export([mod_is_supervisable/1, 16 | mod_has_fun/2, 17 | mod_has_deps/1, 18 | rpc_ok_pid/6, 19 | rpc_ok/6, 20 | iso_8601_fmt/1, 21 | datetime_stamp/1 22 | ]). 23 | 24 | 25 | mod_is_supervisable(Mod) -> 26 | mod_has_fun(Mod,{sup,0}). 27 | 28 | mod_has_deps(Mod) -> 29 | mod_has_fun(Mod,{deps,0}). 30 | 31 | mod_has_fun(Mod,{Name,Arity}) -> 32 | lists:member({Name,Arity},Mod:module_info(exports)). 33 | 34 | 35 | 36 | rpc_ok_pid(Node,Mod,Fun,Arg,OnError,OnOk) -> 37 | case rpc:call(Node,Mod,Fun,Arg) of 38 | {badrpc,Error} -> 39 | OnError(Error); 40 | Result -> 41 | case Result of 42 | {ok,Pid} -> 43 | OnOk(Pid); 44 | ignore -> 45 | OnOk(ignore); 46 | {error, Error} -> 47 | OnError(Error) 48 | end 49 | end. 50 | 51 | 52 | rpc_ok(Node,Mod,Fun,Arg,OnError,OnOk) -> 53 | case rpc:call(Node,Mod,Fun,Arg) of 54 | {badrpc,Error} -> 55 | OnError(Error); 56 | Result -> 57 | case Result of 58 | ok -> 59 | OnOk(); 60 | Error -> 61 | OnError(Error) 62 | end 63 | end. 64 | 65 | 66 | iso_8601_fmt({{Y,M,D},{H,Mi,S}}) -> 67 | lists:flatten(io_lib:format("~4.10.0B-~2.10.0B-~2.10.0B ~2.10.0B:~2.10.0B:~2.10.0B", 68 | [Y, M, D, H, Mi, S])). 69 | datetime_stamp({{Y,M,D},{H,Mi,S}}) -> 70 | lists:flatten(io_lib:format("~4.10.0B~2.10.0B~2.10.0B~2.10.0B~2.10.0B~2.10.0B", 71 | [Y, M, D, H, Mi, S])). 72 | -------------------------------------------------------------------------------- /src/getopt.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author Juan Jose Comellas 3 | %%% @copyright (C) 2009 Juan Jose Comellas 4 | %%% @doc Parses command line options with a format similar to that of GNU getopt. 5 | %%% @end 6 | %%% 7 | %%% This source file is subject to the New BSD License. You should have received 8 | %%% a copy of the New BSD license with this software. If not, it can be 9 | %%% retrieved from: http://www.opensource.org/licenses/bsd-license.php 10 | %%%------------------------------------------------------------------- 11 | -module(getopt). 12 | -author('juanjo@comellas.org'). 13 | 14 | -export([parse/2, usage/2, usage/3, usage/4]). 15 | 16 | 17 | -define(TAB_LENGTH, 8). 18 | %% Indentation of the help messages in number of tabs. 19 | -define(INDENTATION, 3). 20 | 21 | %% Position of each field in the option specification tuple. 22 | -define(OPT_NAME, 1). 23 | -define(OPT_SHORT, 2). 24 | -define(OPT_LONG, 3). 25 | -define(OPT_ARG, 4). 26 | -define(OPT_HELP, 5). 27 | 28 | -define(IS_OPT_SPEC(Opt), (tuple_size(Opt) =:= ?OPT_HELP)). 29 | 30 | 31 | %% Atom indicating the data type that an argument can be converted to. 32 | -type arg_type() :: 'atom' | 'binary' | 'boolean' | 'float' | 'integer' | 'string'. 33 | %% Data type that an argument can be converted to. 34 | -type arg_value() :: atom() | binary() | boolean() | float() | integer() | string(). 35 | %% Argument specification. 36 | -type arg_spec() :: arg_type() | {arg_type(), arg_value()} | undefined. 37 | %% Option type and optional default argument. 38 | -type simple_option() :: atom(). 39 | -type compound_option() :: {atom(), arg_value()}. 40 | -type option() :: simple_option() | compound_option(). 41 | %% Command line option specification. 42 | -type option_spec() :: { 43 | Name :: atom(), 44 | Short :: char() | undefined, 45 | Long :: string() | undefined, 46 | ArgSpec :: arg_spec(), 47 | Help :: string() | undefined 48 | }. 49 | 50 | 51 | %% @doc Parse the command line options and arguments returning a list of tuples 52 | %% and/or atoms using the Erlang convention for sending options to a 53 | %% function. 54 | -spec parse([option_spec()], string() | [string()]) -> 55 | {ok, {[option()], [string()]}} | {error, {Reason :: atom(), Data :: any()}}. 56 | parse(OptSpecList, CmdLine) -> 57 | try 58 | Args = if 59 | is_integer(hd(CmdLine)) -> 60 | string:tokens(CmdLine, " \t\n"); 61 | true -> 62 | CmdLine 63 | end, 64 | parse(OptSpecList, [], [], 0, Args) 65 | catch 66 | throw: {error, {_Reason, _Data}} = Error -> 67 | Error 68 | end. 69 | 70 | 71 | -spec parse([option_spec()], [option()], [string()], integer(), [string()]) -> 72 | {ok, {[option()], [string()]}}. 73 | %% Process the option terminator. 74 | parse(OptSpecList, OptAcc, ArgAcc, _ArgPos, ["--" | Tail]) -> 75 | % Any argument present after the terminator is not considered an option. 76 | {ok, {lists:reverse(append_default_options(OptSpecList, OptAcc)), lists:reverse(ArgAcc, Tail)}}; 77 | %% Process long options. 78 | parse(OptSpecList, OptAcc, ArgAcc, ArgPos, [[$-, $- | OptArg] = OptStr | Tail]) -> 79 | parse_option_long(OptSpecList, OptAcc, ArgAcc, ArgPos, Tail, OptStr, OptArg); 80 | %% Process short options. 81 | parse(OptSpecList, OptAcc, ArgAcc, ArgPos, [[$- | [_Char | _] = OptArg] = OptStr | Tail]) -> 82 | parse_option_short(OptSpecList, OptAcc, ArgAcc, ArgPos, Tail, OptStr, OptArg); 83 | %% Process non-option arguments. 84 | parse(OptSpecList, OptAcc, ArgAcc, ArgPos, [Arg | Tail]) -> 85 | case find_non_option_arg(OptSpecList, ArgPos) of 86 | {value, OptSpec} when ?IS_OPT_SPEC(OptSpec) -> 87 | parse(OptSpecList, [convert_option_arg(OptSpec, Arg) | OptAcc], 88 | ArgAcc, ArgPos + 1, Tail); 89 | false -> 90 | parse(OptSpecList, OptAcc, [Arg | ArgAcc], ArgPos, Tail) 91 | end; 92 | parse(OptSpecList, OptAcc, ArgAcc, _ArgPos, []) -> 93 | % Once we have completed gathering the options we add the ones that were 94 | % not present but had default arguments in the specification. 95 | {ok, {lists:reverse(append_default_options(OptSpecList, OptAcc)), lists:reverse(ArgAcc)}}. 96 | 97 | 98 | %% @doc Parse a long option, add it to the option accumulator and continue 99 | %% parsing the rest of the arguments recursively. 100 | %% A long option can have the following syntax: 101 | %% --foo Single option 'foo', no argument 102 | %% --foo=bar Single option 'foo', argument "bar" 103 | %% --foo bar Single option 'foo', argument "bar" 104 | -spec parse_option_long([option_spec()], [option()], [string()], integer(), [string()], string(), string()) -> 105 | {ok, {[option()], [string()]}}. 106 | parse_option_long(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, OptArg) -> 107 | case split_assigned_arg(OptArg) of 108 | {Long, Arg} -> 109 | % Get option that has its argument within the same string 110 | % separated by an equal ('=') character (e.g. "--port=1000"). 111 | parse_option_assigned_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, Long, Arg); 112 | 113 | Long -> 114 | case lists:keysearch(Long, ?OPT_LONG, OptSpecList) of 115 | {value, {Name, _Short, Long, undefined, _Help}} -> 116 | parse(OptSpecList, [Name | OptAcc], ArgAcc, ArgPos, Args); 117 | 118 | {value, {_Name, _Short, Long, _ArgSpec, _Help} = OptSpec} -> 119 | % The option argument string is empty, but the option requires 120 | % an argument, so we look into the next string in the list. 121 | parse_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptSpec); 122 | false -> 123 | throw({error, {invalid_option, OptStr}}) 124 | end 125 | end. 126 | 127 | 128 | %% @doc Parse an option where the argument is 'assigned' in the same string using 129 | %% the '=' character, add it to the option accumulator and continue parsing the 130 | %% rest of the arguments recursively. This syntax is only valid for long options. 131 | -spec parse_option_assigned_arg([option_spec()], [option()], [string()], integer(), 132 | [string()], string(), string(), string()) -> 133 | {ok, {[option()], [string()]}}. 134 | parse_option_assigned_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, Long, Arg) -> 135 | case lists:keysearch(Long, ?OPT_LONG, OptSpecList) of 136 | {value, {_Name, _Short, Long, ArgSpec, _Help} = OptSpec} -> 137 | case ArgSpec of 138 | undefined -> 139 | throw({error, {invalid_option_arg, OptStr}}); 140 | _ -> 141 | parse(OptSpecList, [convert_option_arg(OptSpec, Arg) | OptAcc], ArgAcc, ArgPos, Args) 142 | end; 143 | false -> 144 | throw({error, {invalid_option, OptStr}}) 145 | end. 146 | 147 | 148 | %% @doc Split an option string that may contain an option with its argument 149 | %% separated by an equal ('=') character (e.g. "port=1000"). 150 | -spec split_assigned_arg(string()) -> {Name :: string(), Arg :: string()} | string(). 151 | split_assigned_arg(OptStr) -> 152 | split_assigned_arg(OptStr, OptStr, []). 153 | 154 | split_assigned_arg(_OptStr, [$= | Tail], Acc) -> 155 | {lists:reverse(Acc), Tail}; 156 | split_assigned_arg(OptStr, [Char | Tail], Acc) -> 157 | split_assigned_arg(OptStr, Tail, [Char | Acc]); 158 | split_assigned_arg(OptStr, [], _Acc) -> 159 | OptStr. 160 | 161 | 162 | %% @doc Parse a short option, add it to the option accumulator and continue 163 | %% parsing the rest of the arguments recursively. 164 | %% A short option can have the following syntax: 165 | %% -a Single option 'a', no argument or implicit boolean argument 166 | %% -a foo Single option 'a', argument "foo" 167 | %% -afoo Single option 'a', argument "foo" 168 | %% -abc Multiple options: 'a'; 'b'; 'c' 169 | %% -bcafoo Multiple options: 'b'; 'c'; 'a' with argument "foo" 170 | -spec parse_option_short([option_spec()], [option()], [string()], integer(), [string()], string(), string()) -> 171 | {ok, {[option()], [string()]}}. 172 | parse_option_short(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptStr, [Short | Arg]) -> 173 | case lists:keysearch(Short, ?OPT_SHORT, OptSpecList) of 174 | {value, {Name, Short, _Long, undefined, _Help}} -> 175 | parse_option_short(OptSpecList, [Name | OptAcc], ArgAcc, ArgPos, Args, OptStr, Arg); 176 | 177 | {value, {_Name, Short, _Long, ArgSpec, _Help} = OptSpec} -> 178 | case Arg of 179 | [] -> 180 | % The option argument string is empty, but the option requires 181 | % an argument, so we look into the next string in the list. 182 | parse_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, OptSpec); 183 | 184 | _ -> 185 | case is_valid_arg(ArgSpec, Arg) of 186 | true -> 187 | parse(OptSpecList, [convert_option_arg(OptSpec, Arg) | OptAcc], ArgAcc, ArgPos, Args); 188 | _ -> 189 | parse_option_short(OptSpecList, [convert_option_no_arg(OptSpec) | OptAcc], ArgAcc, ArgPos, Args, OptStr, Arg) 190 | end 191 | end; 192 | 193 | false -> 194 | throw({error, {invalid_option, OptStr}}) 195 | end; 196 | parse_option_short(OptSpecList, OptAcc, ArgAcc, ArgPos, Args, _OptStr, []) -> 197 | parse(OptSpecList, OptAcc, ArgAcc, ArgPos, Args). 198 | 199 | 200 | %% @doc Retrieve the argument for an option from the next string in the list of 201 | %% command-line parameters. 202 | parse_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, [Arg | Tail] = Args, {Name, _Short, _Long, ArgSpec, _Help} = OptSpec) -> 203 | % Special case for booleans: when the next string is an option we assume 204 | % the value is 'true'. 205 | case (arg_spec_type(ArgSpec) =:= boolean) andalso not is_boolean_arg(Arg) of 206 | true -> 207 | parse(OptSpecList, [{Name, true} | OptAcc], ArgAcc, ArgPos, Args); 208 | _ -> 209 | parse(OptSpecList, [convert_option_arg(OptSpec, Arg) | OptAcc], ArgAcc, ArgPos, Tail) 210 | end; 211 | parse_option_next_arg(OptSpecList, OptAcc, ArgAcc, ArgPos, [] = Args, {Name, _Short, _Long, ArgSpec, _Help}) -> 212 | % Special case for booleans: when the next string is missing we assume the 213 | % value is 'true'. 214 | case arg_spec_type(ArgSpec) of 215 | boolean -> 216 | parse(OptSpecList, [{Name, true} | OptAcc], ArgAcc, ArgPos, Args); 217 | _ -> 218 | throw({error, {missing_option_arg, Name}}) 219 | end. 220 | 221 | 222 | %% @doc Find the option for the discrete argument in position specified in the 223 | %% Pos argument. 224 | -spec find_non_option_arg([option_spec()], integer()) -> {value, option_spec()} | false. 225 | find_non_option_arg([{_Name, undefined, undefined, _ArgSpec, _Help} = OptSpec | _Tail], 0) -> 226 | {value, OptSpec}; 227 | find_non_option_arg([{_Name, undefined, undefined, _ArgSpec, _Help} | Tail], Pos) -> 228 | find_non_option_arg(Tail, Pos - 1); 229 | find_non_option_arg([_Head | Tail], Pos) -> 230 | find_non_option_arg(Tail, Pos); 231 | find_non_option_arg([], _Pos) -> 232 | false. 233 | 234 | 235 | %% @doc Append options that were not present in the command line arguments with 236 | %% their default arguments. 237 | -spec append_default_options([option_spec()], [option()]) -> [option()]. 238 | append_default_options([{Name, _Short, _Long, {_Type, DefaultArg}, _Help} | Tail], OptAcc) -> 239 | append_default_options(Tail, 240 | case lists:keymember(Name, 1, OptAcc) of 241 | false -> 242 | [{Name, DefaultArg} | OptAcc]; 243 | _ -> 244 | OptAcc 245 | end); 246 | %% For options with no default argument. 247 | append_default_options([_Head | Tail], OptAcc) -> 248 | append_default_options(Tail, OptAcc); 249 | append_default_options([], OptAcc) -> 250 | OptAcc. 251 | 252 | 253 | -spec convert_option_no_arg(option_spec()) -> compound_option(). 254 | convert_option_no_arg({Name, _Short, _Long, ArgSpec, _Help}) -> 255 | case ArgSpec of 256 | % Special case for booleans: if there is no argument we assume 257 | % the value is 'true'. 258 | {boolean, _DefaultValue} -> 259 | {Name, true}; 260 | boolean -> 261 | {Name, true}; 262 | _ -> 263 | throw({error, {missing_option_arg, Name}}) 264 | end. 265 | 266 | 267 | %% @doc Convert the argument passed in the command line to the data type 268 | %% indicated by the argument specification. 269 | -spec convert_option_arg(option_spec(), string()) -> compound_option(). 270 | convert_option_arg({Name, _Short, _Long, ArgSpec, _Help}, Arg) -> 271 | try 272 | {Name, to_type(arg_spec_type(ArgSpec), Arg)} 273 | catch 274 | error:_ -> 275 | throw({error, {invalid_option_arg, {Name, Arg}}}) 276 | end. 277 | 278 | 279 | %% @doc Retrieve the data type form an argument specification. 280 | -spec arg_spec_type(arg_spec()) -> arg_type() | undefined. 281 | arg_spec_type({Type, _DefaultArg}) -> 282 | Type; 283 | arg_spec_type(Type) when is_atom(Type) -> 284 | Type. 285 | 286 | 287 | %% @doc Convert an argument string to its corresponding data type. 288 | -spec to_type(arg_type(), string()) -> arg_value(). 289 | to_type(binary, Arg) -> 290 | list_to_binary(Arg); 291 | to_type(atom, Arg) -> 292 | list_to_atom(Arg); 293 | to_type(integer, Arg) -> 294 | list_to_integer(Arg); 295 | to_type(float, Arg) -> 296 | list_to_float(Arg); 297 | to_type(boolean, Arg) -> 298 | LowerArg = string:to_lower(Arg), 299 | case is_arg_true(LowerArg) of 300 | true -> 301 | true; 302 | _ -> 303 | case is_arg_false(LowerArg) of 304 | true -> 305 | false; 306 | false -> 307 | erlang:error(badarg) 308 | end 309 | end; 310 | to_type(_Type, Arg) -> 311 | Arg. 312 | 313 | 314 | -spec is_arg_true(string()) -> boolean(). 315 | is_arg_true(Arg) -> 316 | (Arg =:= "true") orelse (Arg =:= "t") orelse 317 | (Arg =:= "yes") orelse (Arg =:= "y") orelse 318 | (Arg =:= "on") orelse (Arg =:= "enabled") orelse 319 | (Arg =:= "1"). 320 | 321 | 322 | -spec is_arg_false(string()) -> boolean(). 323 | is_arg_false(Arg) -> 324 | (Arg =:= "false") orelse (Arg =:= "f") orelse 325 | (Arg =:= "no") orelse (Arg =:= "n") orelse 326 | (Arg =:= "off") orelse (Arg =:= "disabled") orelse 327 | (Arg =:= "0"). 328 | 329 | 330 | -spec is_valid_arg(arg_spec(), nonempty_string()) -> boolean(). 331 | is_valid_arg({Type, _DefaultArg}, Arg) -> 332 | is_valid_arg(Type, Arg); 333 | is_valid_arg(boolean, Arg) -> 334 | is_boolean_arg(Arg); 335 | is_valid_arg(integer, Arg) -> 336 | is_integer_arg(Arg); 337 | is_valid_arg(float, Arg) -> 338 | is_float_arg(Arg); 339 | is_valid_arg(_Type, _Arg) -> 340 | true. 341 | 342 | 343 | -spec is_boolean_arg(string()) -> boolean(). 344 | is_boolean_arg(Arg) -> 345 | LowerArg = string:to_lower(Arg), 346 | is_arg_true(LowerArg) orelse is_arg_false(LowerArg). 347 | 348 | 349 | -spec is_integer_arg(string()) -> boolean(). 350 | is_integer_arg([Head | Tail]) when Head >= $0, Head =< $9 -> 351 | is_integer_arg(Tail); 352 | is_integer_arg([_Head | _Tail]) -> 353 | false; 354 | is_integer_arg([]) -> 355 | true. 356 | 357 | 358 | -spec is_float_arg(string()) -> boolean(). 359 | is_float_arg([Head | Tail]) when (Head >= $0 andalso Head =< $9) orelse Head =:= $. -> 360 | is_float_arg(Tail); 361 | is_float_arg([_Head | _Tail]) -> 362 | false; 363 | is_float_arg([]) -> 364 | true. 365 | 366 | 367 | %% @doc Show a message on stdout indicating the command line options and 368 | %% arguments that are supported by the program. 369 | -spec usage([option_spec()], string()) -> ok. 370 | usage(OptSpecList, ProgramName) -> 371 | io:format("Usage: ~s~s~n~n~s~n", 372 | [ProgramName, usage_cmd_line(OptSpecList), usage_options(OptSpecList)]). 373 | 374 | 375 | %% @doc Show a message on stdout indicating the command line options and 376 | %% arguments that are supported by the program. The CmdLineTail argument 377 | %% is a string that is added to the end of the usage command line. 378 | -spec usage([option_spec()], string(), string()) -> ok. 379 | usage(OptSpecList, ProgramName, CmdLineTail) -> 380 | io:format("Usage: ~s~s ~s~n~n~s~n", 381 | [ProgramName, usage_cmd_line(OptSpecList), CmdLineTail, usage_options(OptSpecList)]). 382 | 383 | 384 | %% @doc Show a message on stdout indicating the command line options and 385 | %% arguments that are supported by the program. The CmdLineTail and OptionsTail 386 | %% arguments are a string that is added to the end of the usage command line 387 | %% and a list of tuples that are added to the end of the options' help lines. 388 | -spec usage([option_spec()], string(), string(), [{string(), string()}]) -> ok. 389 | usage(OptSpecList, ProgramName, CmdLineTail, OptionsTail) -> 390 | UsageOptions = lists:foldl( 391 | fun ({Prefix, Help}, Acc) -> 392 | add_option_help(Prefix, Help, Acc) 393 | end, usage_options_reverse(OptSpecList, []), OptionsTail), 394 | io:format("Usage: ~s~s ~s~n~n~s~n", 395 | [ProgramName, usage_cmd_line(OptSpecList), CmdLineTail, 396 | lists:flatten(lists:reverse(UsageOptions))]). 397 | 398 | 399 | %% @doc Return a string with the syntax for the command line options and 400 | %% arguments. 401 | -spec usage_cmd_line([option_spec()]) -> string(). 402 | usage_cmd_line(OptSpecList) -> 403 | usage_cmd_line(OptSpecList, []). 404 | 405 | usage_cmd_line([{Name, Short, Long, ArgSpec, _Help} | Tail], Acc) -> 406 | CmdLine = 407 | case ArgSpec of 408 | undefined -> 409 | if 410 | % For options with short form and no argument. 411 | Short =/= undefined -> 412 | [$\s, $[, $-, Short, $]]; 413 | % For options with only long form and no argument. 414 | Long =/= undefined -> 415 | [$\s, $[, $-, $-, Long, $]]; 416 | true -> 417 | [] 418 | end; 419 | _ -> 420 | if 421 | % For options with short form and argument. 422 | Short =/= undefined -> 423 | [$\s, $[, $-, Short, $\s, $<, atom_to_list(Name), $>, $]]; 424 | % For options with only long form and argument. 425 | Long =/= undefined -> 426 | [$\s, $[, $-, $-, Long, $\s, $<, atom_to_list(Name), $>, $]]; 427 | % For options with neither short nor long form and argument. 428 | true -> 429 | [$\s, $<, atom_to_list(Name), $>] 430 | end 431 | end, 432 | usage_cmd_line(Tail, [CmdLine | Acc]); 433 | usage_cmd_line([], Acc) -> 434 | lists:flatten(lists:reverse(Acc)). 435 | 436 | 437 | %% @doc Return a string with the help message for each of the options and 438 | %% arguments. 439 | -spec usage_options([option_spec()]) -> string(). 440 | usage_options(OptSpecList) -> 441 | lists:flatten(lists:reverse(usage_options_reverse(OptSpecList, []))). 442 | 443 | usage_options_reverse([{Name, Short, Long, _ArgSpec, Help} | Tail], Acc) -> 444 | Prefix = 445 | case Long of 446 | undefined -> 447 | case Short of 448 | % Neither short nor long form (non-option argument). 449 | undefined -> 450 | [$<, atom_to_list(Name), $>]; 451 | % Only short form. 452 | _ -> 453 | [$-, Short] 454 | end; 455 | _ -> 456 | case Short of 457 | % Only long form. 458 | undefined -> 459 | [$-, $- | Long]; 460 | % Both short and long form. 461 | _ -> 462 | [$-, Short, $,, $\s, $-, $- | Long] 463 | end 464 | end, 465 | usage_options_reverse(Tail, add_option_help(Prefix, Help, Acc)); 466 | usage_options_reverse([], Acc) -> 467 | Acc. 468 | 469 | 470 | %% @doc Add the help message corresponding to an option specification to a list 471 | %% with the correct indentation. 472 | -spec add_option_help(Prefix :: string(), Help :: string(), Acc :: string()) -> string(). 473 | add_option_help(Prefix, Help, Acc) when is_list(Help), Help =/= [] -> 474 | FlatPrefix = lists:flatten(Prefix), 475 | case ((?INDENTATION * ?TAB_LENGTH) - 2 - length(FlatPrefix)) of 476 | TabSize when TabSize > 0 -> 477 | Tab = lists:duplicate(ceiling(TabSize / ?TAB_LENGTH), $\t), 478 | [[$\s, $\s, FlatPrefix, Tab, Help, $\n] | Acc]; 479 | _ -> 480 | % The indentation for the option description is 3 tabs (i.e. 24 characters) 481 | % IMPORTANT: Change the number of tabs below if you change the 482 | % value of the INDENTATION macro. 483 | [[$\t, $\t, $\t, Help, $\n], [$\s, $\s, FlatPrefix, $\n] | Acc] 484 | end; 485 | add_option_help(_Opt, _Prefix, Acc) -> 486 | Acc. 487 | 488 | 489 | 490 | %% @doc Return the smallest integral value not less than the argument. 491 | -spec ceiling(float()) -> integer(). 492 | ceiling(X) -> 493 | T = erlang:trunc(X), 494 | case (X - T) of 495 | % Neg when Neg < 0 -> 496 | % T; 497 | Pos when Pos > 0 -> 498 | T + 1; 499 | _ -> 500 | T 501 | end. 502 | --------------------------------------------------------------------------------