Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

kstat addon for node.js

  • Loading branch information...
commit d2ca786796f0b986c5bcc43d35770638b843bb0c 0 parents
@bcantrill authored
Showing with 614 additions and 0 deletions.
  1. +18 −0 LICENSE
  2. +64 −0 README
  3. +57 −0 example.js
  4. +303 −0 kstat.cc
  5. +156 −0 mpstat.js
  6. +16 −0 wscript
18 LICENSE
@@ -0,0 +1,18 @@
+Copyright 2010 Bryan Cantrill. All rights reserved.
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
64 README
@@ -0,0 +1,64 @@
+
+kstat, a node.js addon for reading kstats
+-----------------------------------------
+
+This is a simple node.js addon that allows one to read kernel statistics via
+the kstat framework on Solaris. The "kstat" module exports a single class,
+"Reader" that has the following methods:
+
+ Reader(): Takes an optional object specifying the kstats to read. This
+ object may have the following members:
+
+ class => optional string denoting class of kstat(s) to read
+ module => optional string denoting module of kstat(s) to read
+ name => optional string denoting name of kstat(s) to read
+ instance => optional integer denoting instance of kstat(s) to read
+
+ Together, these members form a specification of kstats to read.
+
+ read(): Returns an array of kstats that match the specification with
+ which the reader instance was constructed. Each element of the
+ array is an object that contains the following members:
+
+ class => string denoting class of kstat
+ module => string denoting module of kstat
+ name => string denoting name of kstat
+ instance => integer denoting instance of kstat
+ snaptime => nanoseconds since boot of this snapshot
+ data => an object containing the named kstat data itself
+
+For example, here is a simple node.js program that dumps the kstats of
+class 'mib2':
+
+ var kstat = require('kstat');
+ var sys = require('sys');
+ var reader = new kstat.Reader({ 'class': 'mib2' } );
+ sys.puts(sys.inspect(reader.read()));
+
+Here is a the same program that reads only the 'mib2' class kstats from
+the 'icmp' module:
+
+ var kstat = require('kstat');
+ var sys = require('sys');
+ var reader = new kstat.Reader({ 'class': 'mib2', module: 'icmp' } );
+ sys.puts(sys.inspect(reader.read()));
+
+Finally, here is a simple program that prints the number of ICMP datagrams
+received per second:
+
+ var kstat = require('kstat');
+ var sys = require('sys');
+ var reader = new kstat.Reader({ 'class': 'mib2', module: 'icmp' } );
+
+ var data = [];
+ var gen = 0;
+
+ setInterval(function() {
+ data[gen] = reader.read()[0];
+ gen ^= 1;
+
+ if (!(data[0] && data[1]))
+ return;
+
+ sys.puts(data[gen ^ 1].data.inDatagrams - data[gen].data.inDatagrams);
+ }, 1000);
57 example.js
@@ -0,0 +1,57 @@
+/*
+ * A simple demonstration of the kstat reader that reads every kstat and
+ * prints module, class, name and instance.
+ */
+
+var sys = require('sys');
+var kstat = require('kstat');
+
+var fixed = function (str, len) {
+ var rval = str, i;
+
+ for (i = 0; i < len - str.length; i++)
+ rval += ' ';
+
+ return (rval);
+};
+
+var reader = new kstat.Reader();
+
+var data = reader.read();
+
+var fields = {
+ module: 20,
+ 'class': 20,
+ name: 30,
+ instance: 8
+};
+
+var str = '';
+
+for (f in fields)
+ str += fixed(f.toUpperCase(), fields[f]);
+
+sys.puts(str);
+
+data.sort(function (l, r) {
+ for (f in fields) {
+ if (f == 'instance')
+ return (l[f] - r[f]);
+
+ var rval = l[f].localeCompare(r[f]);
+
+ if (rval)
+ return (rval);
+ }
+
+ return (0);
+});
+
+for (i = 0; i < data.length; i++) {
+ str = '';
+
+ for (f in fields)
+ str += fixed(data[i][f], fields[f] + '');
+
+ sys.puts(str);
+}
303 kstat.cc
@@ -0,0 +1,303 @@
+#include <v8.h>
+#include <node.h>
+#include <string.h>
+#include <unistd.h>
+#include <node_object_wrap.h>
+#include <kstat.h>
+#include <errno.h>
+#include <string>
+#include <vector>
+#include <sys/varargs.h>
+
+using namespace v8;
+using std::string;
+using std::vector;
+
+/*
+ * Some helper routines useful for those of us who would really prefer a
+ * C API... ;)
+ */
+string *
+stringMember(Local<Value> value, char *member, char *deflt)
+{
+ if (!value->IsObject())
+ return (new string (deflt));
+
+ Local<Object> o = Local<Object>::Cast(value);
+ Local<Value> v = o->Get(String::New(member));
+
+ if (!v->IsString())
+ return (new string (deflt));
+
+ String::AsciiValue val(v);
+ return (new string(*val));
+}
+
+int64_t
+intMember(Local<Value> value, char *member, int64_t deflt)
+{
+ int64_t rval = deflt;
+
+ if (!value->IsObject())
+ return (rval);
+
+ Local<Object> o = Local<Object>::Cast(value);
+ value = o->Get(String::New(member));
+
+ if (!value->IsNumber())
+ return (rval);
+
+ Local<Integer> i = Local<Integer>::Cast(value);
+
+ return (i->Value());
+}
+
+class KStatReader : node::ObjectWrap {
+public:
+ static void Initialize(Handle<Object> target);
+
+protected:
+ static Persistent<FunctionTemplate> KStatReader::templ;
+
+ KStatReader(string *module, string *classname,
+ string *name, int instance);
+ Handle<Value> error(const char *fmt, ...);
+ Handle<Value> read(kstat_t *);
+ int update();
+ ~KStatReader();
+
+ static Handle<Value> New(const Arguments& args);
+ static Handle<Value> Read(const Arguments& args);
+ static Handle<Value> Update(const Arguments& args);
+
+
+private:
+ string *ksr_module;
+ string *ksr_class;
+ string *ksr_name;
+ int ksr_instance;
+ kid_t ksr_kid;
+ kstat_ctl_t *ksr_ctl;
+ vector<kstat_t *> ksr_kstats;
+};
+
+Persistent<FunctionTemplate> KStatReader::templ;
+
+KStatReader::KStatReader(string *module, string *classname,
+ string *name, int instance)
+ : node::ObjectWrap(), ksr_module(module), ksr_class(classname),
+ ksr_name(name), ksr_instance(instance), ksr_kid(-1)
+{
+ if ((ksr_ctl = kstat_open()) == NULL)
+ throw "could not open kstat";
+};
+
+KStatReader::~KStatReader()
+{
+ delete ksr_module;
+ delete ksr_class;
+ delete ksr_name;
+}
+
+int
+KStatReader::update()
+{
+ kstat_t *ksp;
+ kid_t kid;
+
+ if ((kid = kstat_chain_update(ksr_ctl)) == 0 && ksr_kid != -1)
+ return (0);
+
+ if (kid == -1)
+ return (-1);
+
+ ksr_kid = kid;
+ ksr_kstats.clear();
+
+ for (ksp = ksr_ctl->kc_chain; ksp != NULL; ksp = ksp->ks_next) {
+ if (!ksr_module->empty() &&
+ ksr_module->compare(ksp->ks_module) != 0)
+ continue;
+
+ if (!ksr_class->empty() &&
+ ksr_class->compare(ksp->ks_class) != 0)
+ continue;
+
+ if (!ksr_name->empty() && ksr_name->compare(ksp->ks_name) != 0)
+ continue;
+
+ if (ksr_instance != -1 && ksp->ks_instance != ksr_instance)
+ continue;
+
+ ksr_kstats.push_back(ksp);
+ }
+
+ return (0);
+}
+
+void
+KStatReader::Initialize(Handle<Object> target)
+{
+ HandleScope scope;
+
+ Local<FunctionTemplate> k = FunctionTemplate::New(KStatReader::New);
+
+ templ = Persistent<FunctionTemplate>::New(k);
+ templ->InstanceTemplate()->SetInternalFieldCount(1);
+ templ->SetClassName(String::NewSymbol("Reader"));
+
+ NODE_SET_PROTOTYPE_METHOD(templ, "read", KStatReader::Read);
+
+ target->Set(String::NewSymbol("Reader"), templ->GetFunction());
+}
+
+Handle<Value>
+KStatReader::New(const Arguments& args)
+{
+ HandleScope scope;
+
+ KStatReader *k = new KStatReader(stringMember(args[0], "module", ""),
+ stringMember(args[0], "class", ""),
+ stringMember(args[0], "name", ""),
+ intMember(args[0], "instance", -1));
+
+ k->Wrap(args.Holder());
+
+ return (args.This());
+}
+
+Handle<Value>
+KStatReader::error(const char *fmt, ...)
+{
+ char buf[1024], buf2[1024];
+ char *err = buf;
+ va_list ap;
+
+ va_start(ap, fmt);
+ (void) vsnprintf(buf, sizeof (buf), fmt, ap);
+
+ if (buf[strlen(buf) - 1] != '\n') {
+ /*
+ * If our error doesn't end in a new-line, we'll append the
+ * strerror of errno.
+ */
+ (void) snprintf(err = buf2, sizeof (buf2),
+ "%s: %s", buf, strerror(errno));
+ } else {
+ buf[strlen(buf) - 1] = '\0';
+ }
+
+ return (ThrowException(Exception::Error(String::New(err))));
+}
+
+Handle<Value>
+KStatReader::read(kstat_t *ksp)
+{
+ Local<Object> rval = Object::New();
+ Local<Object> data;
+ kstat_named_t *nm;
+ int i;
+
+ rval->Set(String::New("class"), String::New(ksp->ks_class));
+ rval->Set(String::New("module"), String::New(ksp->ks_module));
+ rval->Set(String::New("name"), String::New(ksp->ks_name));
+ rval->Set(String::New("instance"), Integer::New(ksp->ks_instance));
+
+ if (kstat_read(ksr_ctl, ksp, NULL) == -1) {
+ /*
+ * It is deeply annoying, but some kstats can return errors
+ * under otherwise routine conditions. (ACPI is one
+ * offender; there are surely others.) To prevent these
+ * fouled kstats from completely ruining our day, we assign
+ * an "error" member to the return value that consists of
+ * the strerror().
+ */
+ rval->Set(String::New("error"), String::New(strerror(errno)));
+ return (rval);
+ }
+
+ if (ksp->ks_type != KSTAT_TYPE_NAMED)
+ return (rval);
+
+ rval->Set(String::New("instance"), Integer::New(ksp->ks_instance));
+ rval->Set(String::New("snaptime"), Number::New(ksp->ks_snaptime));
+
+ data = Object::New();
+ nm = (kstat_named_t *)ksp->ks_data;
+
+ for (i = 0; i < ksp->ks_ndata; i++, nm++) {
+ Handle<Value> val;
+
+ switch (nm->data_type) {
+ case KSTAT_DATA_CHAR:
+ val = Number::New(nm->value.c[0]);
+ break;
+
+ case KSTAT_DATA_INT32:
+ val = Number::New(nm->value.i32);
+ break;
+
+ case KSTAT_DATA_UINT32:
+ val = Number::New(nm->value.ui32);
+ break;
+
+ case KSTAT_DATA_INT64:
+ val = Number::New(nm->value.i64);
+ break;
+
+ case KSTAT_DATA_UINT64:
+ val = Number::New(nm->value.ui64);
+ break;
+
+ case KSTAT_DATA_STRING:
+ val = String::New(KSTAT_NAMED_STR_PTR(nm));
+ break;
+
+ default:
+ throw (error("unrecognized data type %d for member "
+ "\"%s\" in instance %d of stat \"%s\" (module "
+ "\"%s\", class \"%s\")\n", nm->data_type,
+ nm->name, ksp->ks_instance, ksp->ks_name,
+ ksp->ks_module, ksp->ks_class));
+ }
+
+ data->Set(String::New(nm->name), val);
+ }
+
+ rval->Set(String::New("data"), data);
+
+ return (rval);
+}
+
+Handle<Value>
+KStatReader::Read(const Arguments& args)
+{
+ KStatReader *k = ObjectWrap::Unwrap<KStatReader>(args.Holder());
+ Local<Array> rval;
+ int i;
+
+ if (k->update() == -1) {
+ return (k->error("failed to update kstat chain"));
+
+ char buf[256];
+ (void) sprintf(buf, "kstat_chain_update failed: %s", errno);
+ return (ThrowException(Exception::Error(String::New(buf))));
+ }
+
+ rval = Array::New(k->ksr_kstats.size());
+
+ try {
+ for (i = 0; i < k->ksr_kstats.size(); i++)
+ rval->Set(i, k->read(k->ksr_kstats[i]));
+ } catch (Handle<Value> err) {
+ return (err);
+ }
+
+ return (rval);
+}
+
+extern "C" void
+init (Handle<Object> target)
+{
+ KStatReader::Initialize(target);
+}
156 mpstat.js
@@ -0,0 +1,156 @@
+/*
+ * A node.js implementation of the venerable Solaris command, mpstat(1).
+ * Note that (1) we implement user time, system time and idle time as
+ * nanoseconds not ticks (making it more accurate than mpstat) and (2) wait
+ * time is always zero (operating under the principle that if you can't say
+ * something accurate, you shouldn't say anything at all).
+ */
+
+var sys = require('sys');
+var kstat = require('kstat');
+
+var fields = {
+ CPU: { sys: { value: function (s) { return (s.instance); } } },
+ minf: { vm: { value: [ 'as_fault', 'hat_fault' ] } },
+ mjf: { vm: { value: 'maj_fault' } },
+ xcal: { sys: { value: 'xcalls' } },
+ intr: { sys: { value: 'intr', width: 5 } },
+ ithr: { sys: { value: 'intrthread' } },
+ csw: { sys: { value: 'pswitch', width: 4 } },
+ icsw: { sys: { value: 'inv_swtch' } },
+ migr: { sys: { value: 'cpumigrate' } },
+ smtx: { sys: { value: 'mutex_adenters' } },
+ srw: { sys: { value: [ 'rw_rdfails', 'rw_wrfails' ], width: 4 } },
+ syscl: { sys: { value: 'syscall' } },
+ usr: { sys: { value: 'cpu_nsec_user', time: true } },
+ sys: { sys: { value: 'cpu_nsec_kernel', time: true } },
+ wt: { sys: { value: function () { return (0); }, width: 3 } },
+ idl: { sys: { value: 'cpu_nsec_idle', time: true } }
+};
+
+var reader = {};
+
+for (f in fields) {
+ for (stat in fields[f]) {
+ if (reader[stat])
+ continue;
+
+ reader[stat] = new kstat.Reader({ module: 'cpu',
+ 'class': 'misc', name: stat });
+ }
+}
+
+var pad = function (str, len) {
+ var rval = '', i;
+
+ for (i = 0; i < len - str.length; i++)
+ rval += ' ';
+
+ rval += str;
+
+ return (rval);
+};
+
+var outputheader = function ()
+{
+ var f, s;
+ var str = '';
+
+ for (f in fields) {
+ for (s in fields[f]) {
+ if ((w = fields[f][s].width) !== 0)
+ break;
+ }
+
+ if (str.length > 0)
+ str += ' ';
+
+ str += pad(f, w ? w : f.length);
+ }
+
+ sys.puts(str);
+};
+
+var outputcpu = function (now, last)
+{
+ var f, s;
+ var line = '', i;
+
+ for (f in fields) {
+ for (s in fields[f])
+ stat = fields[f][s];
+
+ if (stat.value instanceof Function) {
+ value = stat.value(now[s], last[s]);
+ } else if (stat.value instanceof Array) {
+ value = 0;
+
+ for (i = 0; i < stat.value.length; i++) {
+ value += (now[s].data[stat.value[i]] -
+ last[s].data[stat.value[i]]);
+ }
+ } else {
+ value = now[s].data[stat.value] -
+ last[s].data[stat.value];
+ }
+
+ if (stat.time) {
+ /*
+ * If this is an expression of percentage of time, we
+ * need to divide by the delta in snap time.
+ */
+ value = parseInt((value / (now[s].snaptime -
+ last[s].snaptime) * 100.0) + '', 10);
+ }
+
+ if (line.length > 0)
+ line += ' ';
+
+ line += pad(value + '', stat.width ? stat.width : f.length);
+ }
+
+ sys.puts(line);
+};
+
+var data = [];
+var gen = 0;
+
+var output = function ()
+{
+ var now = {};
+ var cpus = [];
+ var header = false, i;
+
+ gen = gen ^ 1;
+ data[gen] = {};
+
+ for (stat in reader) {
+ now = reader[stat].read();
+
+ for (i = 0; i < now.length; i++) {
+ var id = now[i].instance;
+
+ if (!data[gen][id]) {
+ cpus.push(id);
+ data[gen][id] = {};
+ }
+
+ data[gen][id][stat] = now[i];
+ }
+ }
+
+ cpus.sort();
+
+ for (i = 0; i < cpus.length; i++) {
+ if (data[gen ^ 1] && data[gen ^ 1][cpus[i]]) {
+ if (!header) {
+ outputheader();
+ header = true;
+ }
+
+ outputcpu(data[gen][cpus[i]], data[gen ^ 1][cpus[i]]);
+ }
+ }
+};
+
+setInterval(output, 1000);
16 wscript
@@ -0,0 +1,16 @@
+srcdir = '.'
+blddir = 'build'
+VERSION = '0.0.1'
+
+def set_options(opt):
+ opt.tool_options('compiler_cxx')
+
+def configure(conf):
+ conf.check_tool('compiler_cxx')
+ conf.check_tool('node_addon')
+
+def build(bld):
+ obj = bld.new_task_gen('cxx', 'shlib', 'node_addon')
+ obj.target = 'kstat'
+ obj.ldflags = '-lkstat'
+ obj.source = 'kstat.cc'
Please sign in to comment.
Something went wrong with that request. Please try again.