cricket-1.0.5/0040755000114300000000000000000010031605175012110 5ustar bertdwheelcricket-1.0.5/doc/0040755000114300000000000000000010031605175012655 5ustar bertdwheelcricket-1.0.5/doc/neta-paper/0040755000114300000000000000000010031605162014705 5ustar bertdwheelcricket-1.0.5/doc/neta-paper/fig1.gif0100644000114300000000000000604707047764266016256 0ustar bertdwheelGIF89a3@ ` @ @@@`@@@@@` `@``````` @` @` @` @`@ @@@`@@@@@ @ @@ @` @ @ @ @ @@@ @@@@@`@@@@@@@@@@`@ `@@`@``@`@`@`@`@@ @@@`@@@@@@ @@@`@@@@@@ @@@`@@@@@@ @@@`@@@@@ @` @ ` @ @@@`@@@@@` `@``````` @` @`ࠀ @` @` @` @ ` @ @@@`@@@@@` `@``````` @` @` @`:n!,3 H*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳOJʠB"`ӧ* իNʵO zKذeӪMya[LFg[5ZhmfwX˙V|\7`pF45xe8m҅gj7l,Ƙ}M(=}("}4UN5G1=*4F{~ɦt~bc?>F@ݯW8iHT 9HJ0H @#":eܝI[!C]P~`Q'>F!Ⳡxh>!nEpXD#N}6E'6 $XA HÇ %NXE!X#E$YҤdž Sd%GCj|YM3OWёZ$SCn͙U+Qa6mSX4~֭ɲq'}ܹufK^}\l[ b=&])[֋sW71g\9tѩAn9Ĭ_T]ѵ`m-ս&\8U׷QsN7٭4e޿O~C}{$ OLϬ$pA+pP0 ڐ PBʐDNPWd6LmS/V14,ajq%z I )ʭC~Ѵ$H d2Ҹ,/aZ.2(sNԱα37Ջk24?AE ,1QLŴMʺqMDN=}tWҧFMsi=Q;j5W3Y5[Ue L}5PjkSoX}uPYmՒ,w\ Dw[ RWXwmwݮ\|ٌ_Vuw`RO ^ab(&x-->tbX\=yZpmdT/te:^8TS6$=uAF gFYgo/;enhK:O]52kVi|眺h[^>^jL%v{+,z[nLpo06S p}pS,ܶÏ#1D(-\QsC}tK7tSW}u[wuc}vkvs}w{wG 7@dC ݭ4y뻟A{^yx^/RDlOΞ& 5f#.:?~+8?-w;೾!}(\Ž4xnO7CZA&VXs `8B R7B1OSbxE,fQ[bKyPxI4P?e\ SWBRr['D$) T-QĻ o6MPPƂ!'H;g8ɇ1T.d +Y?NXSd(F~Pj(} e-?Urkp$f(='FQe7 1^2@ϭjfFayxݑEٿ%Y% p(꧰;\r{ͻ?,f$_1e0m\{+'^IΆk^<ֆr<Ld/߽efױ~ >؟='Ak~}p٫asΛ-WLWL9=,6Ɂl}v4W,_;\o}v'}JmWt>[񢭻ޫʾ(@$XР@&TC%NXE5nD 'I)UdK!eYM9uhOA%0ϢI.e$RQNjUYnxWa*Y:˦e-ʵoΥ 1n]y-_w&3pA6CJr0ʲ;+hC A26 M3qOetG#T;BMiU$$`D+H)uD)̰H +/YTL/L2D-aM/?Q SL Q-'L5GlF,+JKD70I):TQYQ!,I +E${A.{% EeS,Uפ= ֎e-Ye@ku*tY`e()KE(\qcKuK0ԷM}[Ίkj &ն"ꮪoz2y2)hL'̑,1.9W[qw01 a 9q˦<M2%lën!j&oȮ/ar m7Go#Hޯho*O+z.W41P\LetcV>jd'ȍr;dFH1R}!oӔJZ̤&7ɦH (GIR& ;cricket-1.0.5/doc/neta-paper/paper.html0100644000114300000000000011015110005664432016704 0ustar bertdwheel Driving by the Rear-View Mirror: Managing a Network with Cricket

Driving by the Rear-View Mirror:
Managing a Network with Cricket

Jeff R. Allen
WebTV Networks, Inc.

Note: This paper was first published in the proceedings of the 1st Conference on Network Administration, which was held April 6-10, 1999. This paper is copyright 1999 by Jeff Allen. Redistribution of this paper is allowed under the GPL.

Abstract

Cricket is a tool that lets users visualize a set of measurements over time. It was designed to assist network administrators by letting them see and respond to patterns in their network. In this paper, I will describe the need we saw and attempted to resolve by writing Cricket, then describe the solution we came up with. Finally, I will describe some future work we expect to do to make Cricket a more proactive monitoring tool.

The Need

When running a complicated network, there are a multitude of things one needs to keep an eye on. Clearly, immediate concerns like connectivity, link status, and routing stability are top in most administrator's minds. Long-term issues like architecture and technology decisions float in the back of our minds. Often, that leaves no room for the most interesting questions, which help with both short term issues and long term ones. Some of the questions we found ourselves asking about various network components were:

  • What is the current state of a component?
  • What has it been recently?
  • What will it likely be in 5 minutes? In an hour? Is that what we expect it to be?
  • What long-term trends can we discern?

Taking the time to ask these questions allows us to step up a level in our monitoring, and move from a reactive realm (i.e. Is the link to Europe up?) to a more contemplative and predictive realm (i.e. Are we seeing an unexpected burst of traffic to Europe?) It's obviously not possible to definitively say that this makes our job easier and our network run more smoothly, but it certainly seems that way. Simply having the data available to be able to say ``things look OK'' gives us a peace of mind that simply reacting to the network would never give us.

Figure 1 shows a screenshot from Cricket, showing the normal way that data is displayed about a target. In this case, we are looking at the traffic on one of our OC3's, which was operating normally until an outage started a little after noon. From this same view, we can tell that in the last week, the peak bandwidth in use on this link was 60 Mb/sec, on Wednesday evening. By glancing at one page, we can answer many of the questions posed above. By looking at longer time scales (accessed via the links in the upper right) we could answer still other questions about the long-term behavior of the traffic on this link.


Figure 1. An example of Cricket in action. These graphs show an OC3 outage.


One of the reasons it's hard to monitor a network is that there are a lot of different components, each with different operating characteristics and monitoring needs. For instance, in our network, some of the components that we use Cricket to monitor are:

  • WAN and LAN interfaces on routers
  • Router operating state (memory, temperature)
  • Switch (or hub) port bandwidth
  • Rack-mounted modem usage
  • DNS activity
  • Host Components (disk, load average, swap)

Note that we monitor some things (DNS, hosts) that are not directly related to the health of the physical network. In our shop, the ``host folks'' and the ``network folks'' work together very closely. There is no artificial separation between the two. I will focus on monitoring the networking components, however, keep in mind that Cricket is capable of monitoring many types of components.

Just as the components we wish to monitor are varied, so are the ways in which we talk to them. Sometimes, we can simply talk SNMP, then either record the data directly or do a bit of post-processing to derive a rate of some kind. Other times, we fetch data via SNMP, then post-process it in some more complicated way to get a final data point. For instance, to monitor modem bank usage, we fetch the current state of all the modems, and count the number that are off-hook. Sometimes we simply run a shell command on another data-gathering system. For instance, we can use Unix tools like 'wc' or simple Perl scripts to derive a data point from the data already collected by syslogd. Finally, sometimes we want to measure and observe an aggregate of other data sources. In a site with multiple Internet links, it would be interesting, for instance, to plot the total Internet bandwidth across all links.

MRTG to the Rescue

Around the time we1 were bringing up the WebTV network, we fetched and installed a tool called MRTG2 to let us start answering the kinds of questions mentioned above about our WAN links. In fact, MRTG was in use at WebTV Networks before we had brought in any commercial network management tools at all. It was only by using, and coming to depend on the MRTG graphs, that we were able to take a step back and figure out why they were so useful to us. After getting ourselves thoroughly addicted to MRTG, even while attempting to roll out another network management system, we were forced to ask the question, ``What is it that MRTG does that we can't get elsewhere.'' The answer to that question makes up the bulk of the first section: MRTG let us manage our network better than reactive systems.

So, there we were, addicted to MRTG, using it in new situations and in ways it was never designed to be used. MRTG started showing signs that it would not scale to handle the new jobs we wanted to throw at it. Something needed to be done, and quickly. A true addict will not wait patiently for a new drug!

Before we threw out MRTG and started over, we thought it might be a good idea to figure out what it does right, and make certain that whatever we got to replace MRTG could at least do those things. Each view of the data has just enough detail to tell the story, but not enough to hide the good bits. For instance, the weekly views show today and this day last week. There is an enforced data density so that graphs have just enough data to tell an accurate story, but not enough to overwhelm the viewer. In addition, the auto-scaling feature consistently produces readable graphs (though there is a bit of room for improvement on this front). It uses a fixed size database which grows linearly with the number of monitored devices, not time. It needs no extra ``cleanup'' work. Because MRTG is web-based, we have a platform independent tool: all it takes is a web browser to check the status of the network. You can even use a WebTV®-based Internet Unit to see graphs of your network on your home television!

Perhaps more important than all that, though, MRTG passes the ``can it solve real problems'' test:

  • It shows BGP failover very well.
  • It is an invaluable tool in traffic shifting/balancing.
  • It lets us answer the question "How did it look before it broke?"

To see how we use graphs to identify and understand real problems, examine figures 2 and 3. These two graphs are selected snapshots of the graphs available to us during backbone routing instability on one of our peer's networks. During this event, it's clear that about 10 Mb/sec of traffic shifted over to the second peer. We were able to use Cricket to find the nature of the problem, see that the outbound traffic had failed over to non-optimal, but functional link, and finally verify that the traffic returned to normal after the event.


Figure 2. Traffic on a Fast Ethernet handoff to a peer of ours. Their backbone was suffering routing instability at the time.



Figure 3. Traffic on a DS3 to another peer during the routing instability shown in Figure 2.


Finally, Cricket passes the ``manager test'' with flying colors. Managers immediately understand the need for a bandwidth upgrade once they see a graph produced with MRTG. Even more astoundingly, it even passes the ``executive test'' which is very important since executives tend to need to sign orders for OC3's and above. The simple web-based user interface makes it easy to print and share the graphs with management, making it easier for everyone to do their job.

Regardless of all these great features, we identified problems we wanted to address in the next generation system. First, MRTG has a narrow view of the world: all targets have exactly two data sources (used by MRTG for bandwidth in and bandwidth out). We wanted to be able to measure other things about components. MRTG also has severe performance problems, resulting from the way it reads and writes data. MRTG was incrementally developed and the performance problem only became apparent after it was too late to fix it. Usually, it's not an issue, because MRTG is mostly used in small installations. However, with all the different ways we were trying to use MRTG, performance became a problem for us.

There are ways to overcome these performance problems by arranging to run multiple instances of MRTG in parallel. However, the configuration needed to run it in parallel is even more complicated than the usual configuration. For our varied uses, the configuration was already unwieldy and inefficient. The last thing we needed was to make it even more complicated!

Why Not Commercial Tools?

Some might recommend commercial products as a replacement for MRTG, since commercial products are supposed to be designed to scale well and be easy to configure. Our experience is that commercial tools introduce as many problems as they purport to solve, and often leave the original problems (scalable performance and configuration) unsolved. Here's an undoubtedly one-sided list of the problems inherent in most commercial tools that could have helped us.

First and foremost is cost. It's expensive to make good software. No one will argue that point. In fact, it has been more expensive than we expected to develop Cricket. But when buying an expensive network management tool, things get ugly. It takes too much time, paperwork, patience, and persuading management, even if you do have the budget for a commercial tool. Evaluating and rolling out a free software package can happen in an afternoon, if you put your mind to it. There's simply no downside risk - install it and if it solves your problem, keep it. If it doesn't meet your expectations, delete it and move on. You get what you pay for, but sometimes it's what you are not getting (support hassles and licensing nightmares) that really counts.

With commercial tools, database formats are often secret, API's (usually only available at extra cost) are either a pain to work with, or are major performance pigs. Customer support varies, but the simple fact is that even stellar customer support won't do for you what getting your own two hands dirty in the code will do: give you a deep understanding of your NMS and the confidence to trust the system and your ability to operate it. Then, there's the version tango. The NMS doesn't find out about new router models until 6 months after you've already deployed them because the router company and the NMS company are feuding. The trouble-tracking system you are using is only certified to talk to the next version of the NMS that you cannot use yet because that version introduces a critical bug that no other customers are seeing, and thus, will not get repaired until you threaten to revoke your support license - which, by the way, does not expire until next December.

Finally, let's be honest: Free Software rules. Who would you rather trust with the smooth operation of your network, a bunch of folks who are in the same boat as you, or a giant vendor with 100-page implementation plans and slick salesmen? The choice is obvious, to me at least: network managers design better tools to do their job3. Sometimes commercial tools are the right tool for the job, but in this case, it made more sense to go at it on our own. It will make even more sense for your organization, since WebTV Networks has done much of the hard work. All you need to do is download, install and configure Cricket.

The Solution

The solution comes as two pieces, a new database and a new user-visible front-end. By changing out the back-end, we addressed the flexibility and performance goals. By changing out the front-end, we improved the user interface, both for configuring the system and viewing the graphs. The two pieces are developed semi-independently, one by me (in California) and one by the original author of MRTG, Tobias Oetiker (in Switzerland). Keeping the two pieces separate makes it easier to develop them, and it also means the database component can be reused in other contexts. RRD, the database backend, is distributed separately from Cricket4.

We are deeply indebted to Tobias for his hard work on the database back-end. Without it, Cricket would be nothing. By engineering RRD correctly from the start, and by improving it's reliability over the last year, Tobias solved over half our problems with MRTG himself. I simply had the easy job of passing the right data into his code. All the seeming magic of Cricket comes from Tobias's RRD code.

RRD: The Round Robin Database

The next generation back-end is called RRD, the Round Robin Database. It takes over exactly the same jobs the back end provided with MRTG did: storing and rolling up the data and generating graphs from the stored data. RRD is written in C, and comes in both a Perl module and a command line version, which can be used interactively or across a pipe from scripts. It can be used either directly by simple scripts, or via frontends like Cricket. Other frontends are available from the RRD website.

RRD achieves high performance by using binary files to minimize I/O during the common update operation. The ``round robin'' in RRD refers to the fact that RRD uses circular buffers to minimize I/O. RRD is at least one, and possibly two orders of magnitude faster than MRTG's backend, depending on how you measure it. It's truly incredible to watch it chew through data if you have ever seen MRTG trying to handle the same job.

RRD is more flexible than MRTG in at least two dimensions. First, it can take data from an arbitrary number of data sources. MRTG was limited to two datasources. Originally they were reserved for input bandwidth and output bandwidth, though they got co-opted for other measurements by many MRTG hackers. RRD can also keep an arbitrary number of data arrays, each fed at a different rate. For instance, you can keep 600 samples taken every 5 minutes alongside 600 samples taken every 30 minutes. Thus, you can have 5 minute data stretching back 50 hours into the past, and 30 minute data stretching back 12 days. When you draw a graph of this data, you can see recent data in high resolution, and older data in lower resolution. This is one of the original features of MRTG that made us so happy. In RRD this feature is completely configurable.

RRD stores data with higher precision than ever before (float versus integer) which allows us to measure bigger things (OC3's) and smaller things (Unix load averages) without relying on goofy scaling hacks, as we were required to do with MRTG. The data file where this data lives on disk is still a fixed size over time, and scales with the product of the number of datasources and number and sizes of the data arrays. The data storage in use for an entire installation still scales linearly with the number of targets under observation. At WebTV Networks, we have Cricket configured to store 6 variables for every router and switch interface. Each interface requires 174 kilobytes on disk, which is enough room to store all the data for 600 days. Of course, your mileage may vary; Cricket is configurable, so you can choose to keep more or less data, depending on your needs.

RRD still provides the same simple, sparse graphs we grew to love while using MRTG. There is now much more flexibility at the time the graph is generated. For instance, we can choose to show some data sources, but not others. We can choose to scale a data source using an arbitrary mathematical expression. For instance, we can fetch the temperature from a router in Celsius, and present it to the user in Fahrenheit. Since almost all data seems to arrive in exactly the wrong units, it's quite helpful to have this flexibility. Do you think about Ethernet capacity in ``megabytes/sec'' The scaling feature lets us turn that measurement into ``megabits/sec'' It is also possible to integrate data from multiple sources into a single graph. For instance, you could make a summary graph showing the sum of the traffic across all of your Internet links.


Figure 4. A simplified example of a config tree.


The Config Tree

Recall that part of the problem was flexibility and performance of the database, both of which RRD solved. The other part of the problem was scalability of the configuration. Our solution is something called the config tree. A config tree is a set of configuration files organized into a tree. This hierarchical configuration structure allows us to use inheritance to reduce repeated information in the configuration. To simplify implementation of the config tree, the inheritance strictly follows the directory structure; complicated concepts like multiple inheritance are not supported. The attributes that are present in a leaf node are the union of all the attributes in all the nodes on a path leading from the root of the config tree to the leaf node. Lower nodes can override higher nodes, so it's possible to make exceptions in certain subtrees, and do other clever sleight of hand.

It's easier to understand the config tree by looking at an example (see figure 4). Attributes that all of the system will share are located at the root of the config tree. For instance, the length of the polling interval is set there. At the next level, we set attributes that will be restricted to the current subtree. At this level, typically you will find the target type. Finally at the lowest level we set things that will vary on a per-target basis. For instance, we set the interface name that we are trying to measure here. By using the power of inheritance, you can avoid repeating the information at the top of the config tree many times near the bottom of the config tree. The three level config tree in the example is the simplest in common use. The one that ships with Cricket in the sample-config directory works like this. Config trees can and do have many more levels. At WebTV Networks, for instance, we add levels to break apart routers in different data centers to make the directory listings more manageable. There are no built-in limits on the shape of the config tree, only practical ones.

Figure 5 shows the power of the string expansion feature in Cricket. When it comes across strings in the config tree with special markers in them, it expands the strings in much the same way a scripting language expands variables. The sample config tree that ships with Cricket uses this feature in several places to dynamically build strings from settings already available. In the example, the short and long descriptions for the target are set based on some data inherited from high in the tree, and other data related to the target itself. In order to make this feature even more useful, we introduced auto-variables, which are set to certain strings automatically by Cricket. They are available for use by expansions, making it possible to automatically generate things that change for every target, like the data file location. The same expansion system is currently used to make it possible to customize the HTML output of the user interface component, though it is still not yet as flexible as we would like it to be.


Figure 5. An example of variable expansion.


Cricket's collector is single-threaded, and thus spends a fair amount of time waiting for data to arrive over the network. The wall-clock run time for each instance of the collector is limited to the length of the desired polling interval (or else you might miss polling cycles, which would be unfortunate). Thus, the number of targets that can be polled by a single collector on a single host is limited. To get maximum polling capacity, it is necessary to run several collectors in parallel, each working on a different subset of the total set of targets to be polled. This was hardly a surprise to us, since we needed to use the same technique to boost the performance of our MRTG installation. In fact, we designed the config tree to make partitioning the set of targets easy, so parallelizing Cricket is simply a matter of starting the right collectors at the right time, each operating on the right subtrees of the global config tree. There is a wrapper script for the collector which does this, as well as other housekeeping chores like managing logfiles, and checking them for errors.

The config tree has several other advantages we stumbled upon after using it for a while. Because the subtrees are mostly independent from one another, multiple administrators can work on the config tree without impacting each other. The hierarchical structure makes it simple to make standards that apply across all the different applications. It also makes it possible, when necessary, to make exceptions for certain devices. Of course, in a perfect world, this wouldn't be necessary. But this isn't a tool for a perfect world, it's a tool that was written to solve problems in the real world. Sometimes it just happens that we need to query one device with different OID's, or that the community name is different on a certain device. At the same time, though, a properly designed config tree can save you a lot of work when you decide to make some kind of wide-spread change to the system. Instead of changing the data everywhere, you can change it in one place.

Contemplating our fully populated config tree gave us another idea. One of the hardest parts of maintaining a suite of network management products is trying to keep their configurations up to date and in sync with one another. What if we declared, by fiat, that our Cricket config tree was the one true source of configuration information, and that all others should be derived from Cricket's config tree? We decided to design the config tree to be a simple collection of dictionaries. As the config tree is read the parser pays no attention to what keys and values it finds. It simply stores them away. Later, the application which called the parser (Cricket in this case) does lookups in the dictionary looking for data it understands which will control its behavior. Since it is doing lookups into the data, it never even sees data that it's not interested in. In theory, lots of different tools could all rely on the config tree, leveraging the investment made into creating and updating a config tree. In practice, a fair amount of code that belongs in the parser ended up in the Cricket application itself, which means that it will take some reengineering to make the dream of the One True Config Tree come true.

The User Interface

The fastest, most flexible data collection on the planet doesn't help at all if there's no way to see the data. MRTG led us down the right path here; the graphs it makes are simple and useful, and they are presented in a web page viewable on virtually any platform. For Cricket, we decided to make the HTML and the graphs on the fly, instead of caching completed graphs like MRTG does. This saved some of the processing overhead inherent in MRTG's regular polling cycle. As a bonus, using the CGI script instead of pre-generated HTML soundly defeats the over-aggressive browser-side caching that plagues many MRTG installations. On the down side, the startup time for the CGI script can be a problem, as can the graph generation time, both of which the user perceives as lag in the user interface. The startup time problems could be mitigated by the mod_perl Apache module, and Cricket already uses a graph caching scheme to attempt to avoid wasting time re-rendering graphs. It's generally true, though, that the Cricket user interface is slower than the MRTG interface.

The CGI graph browser was specifically designed to maximize flexibility. It is read-only, which keeps security concerns to a minimum. If access control to sensitive information is required, it is currently only possible on a per-installation basis. This is one drawback to the CGI graph browser, but it has not been a problem for us. There are also ways to run a graph browser on a subset of all the collected data, so access controls could probably be implemented by a determined Cricket user. The graph browser operates entirely on URL's which can be incorporated into web pages outside the Cricket system, or stored as bookmarks. This has made it possible to build ``views'' of the Cricket data which are annotated in various ways to assist operations personnel. We can also use these external pages to collect a set of interesting graphs which might be difficult to navigate among inside the graph browser.

To aid in analyzing the data, it can be useful to assemble graphs out of various data streams which are separately collected and stored by Cricket. So far, this capability is limited to putting multiple graphs on one page, and gathering data from several sources and plotting it in summary on a single graph. These advanced features intended to make it easier to look at the data are a tough sell; they tend to be difficult to implement within the current design, and are only used when trying to solve a particular kind of problem. Of course, having the right tool for the job is invaluable, so it's tempting to try to make Cricket do everything under the sun.

While Cricket is the right tool for these analysis jobs, it's not yet infinitely flexible. It presents useful data that answers 90% of the questions, but the jury's still out on the costs and benefits of adding the complexity necessary to answer the last 9% of the questions. What about the last 1%? We'll never get there, and we don't plan to try. Sometimes, the data suggests questions that are better answered with other tools. The trick is knowing when to turn elsewhere to get your answer.

Instance Mapping

MRTG relies on SNMP instance numbers to identify which of the interfaces on a given router it will pick up data from. One of the things we knew we hated about MRTG was the necessity of re-adjusting instance numbers after any router configuration change. It was a small, but persistent, pain in the neck. It's a dreadfully mundane job, and especially annoying on the large routers we use, which have lots of interfaces. Still, it didn't happen that often, and it hardly showed up on the radar screen of ``things we want to improve in MRTG''

To be fair, the instance number problem is not even really MRTG's fault. It's due to an annoying compromise in the SNMP specification that lets network devices get away with murder in the interest of making them easier to implement. This was done under the assumption that polling systems will be smarter than network devices, and can make up for the lack of intelligence in the managed nodes. SNMP devices are allowed to renumber their interfaces behind the poller's back potentially between every single polling cycle. What's a poller to do? The system can either force humans to fix things up when necessary, or it can attempt to figure out the right instance number on it's own. MRTG uses the former strategy, while Cricket has a feature called ``instance mapping'' to implement the latter strategy.

Cricket's instance mapping feature allows administrators to configure a target by name, and then let Cricket do the necessary SNMP lookups to map that name into an instance number. From then on, it's Cricket's job to keep the instance number correct, no matter how hard the device tries to confuse it. Cricket does this magic in a bulletproof but efficient way. To begin with, it uses a series of SNMP GET-NEXT operations (i.e. a table walk) to map the instance name to a number. Once it has a valid instance number, it caches it. The next time it uses the cached instance number, it fetches the name again, along with the polled data. If a ``no such instance'' error is returned, or the name no longer matches, Cricket discards the polled data, maps the name to a new instance number, and fetches the data again. The new instance number is cached, and will be used until the next re-mapping is required. Fetching the name every time like this amounts to a small overhead in the polling transaction, but it ensures that re-mapping happens as soon as necessary, without any human interaction. Presumably commercial systems solve this problem the same way.

It turns out that this is not just a nice feature, it's a required one in some cases. It was designed to be extremely flexible, since I hope to use Cricket to monitor items in the Host MIB which tend to get different instances all the time (disks and processes, among others). To this end, Cricket can even match against names which are regular expressions, making it easy to find a given process, no matter how it was invoked. I have also been told that the virtual interfaces created and destroyed by Cisco's ATM LANE implementation come and go with unpredictable names, making them very hard to monitor with conventional tools. A Cricket user managed to monitor these virtual interfaces for the first time ever using the instance mapping code and regular expression matching. His exact comment on the triumph was ``Instance mapping is the best thing since sliced bread!''

Future Directions

Our hope for Cricket is that it finds a place in other operations centers, and that it helps improve customer service all around. Poor service reflects poorly on all of us, and it's in the industry's best interests to develop useful tools to raise Quality of Service.

The major features we needed from Cricket are in place now, and it has superseded MRTG at WebTV Networks. The bulk of the work is done. However, there is the usual laundry list of ``todo'' items for Cricket. We will do ongoing work on it, especially to integrate patches from interested helpers.

As we have come to depend on graphs to tell us about the state of the WebTV Service, we have stumbled across a principle that should have been obvious to start with. Simply put, lots of graphs are not a proactive tool. It's not reasonable to expect humans to continually review each of the over 4000 graphs we can produce and watch for anomalies. Of course, Cricket was never intended to simply find links that are down, or hosts that are crashed. There are other tools that we already use for that kind of monitoring. Subtle problems in the system manifest themselves as subtle changes in the patterns on the graph. It is these problems that we seek to discover when we humans browse the graphs. We are looking for graphs that don't ``look right''

The obvious solution to the problem of too many graphs is to ask Cricket itself to evaluate the graphs and flag those that don't ``look right'' for further analysis by a human. This is about the time things get really complicated; we could apply all the fancy pattern recognition tools that the computer science community has to offer, including neural networks, signal processing, and fuzzy logic. We took a more pragmatic view and asked the question, ``How do we humans know when it looks right?'' The answer in all cases was that we compare the current behavior to past behavior. Now the problem looks a bit more solvable. We want to create a system of thresholds which are set differently at different times of day. These thresholds will be derived from past data, so that deviation from past patterns results in violated thresholds, which in turn results in an alert that a human sees. With an appropriate GUI, administrators could adjust the suggested thresholds to avoid false positives. They could also mark known anomalous data with the GUI, so that future generated thresholds would not be affected by the ``bad'' data.

We have asked a group of students from Harvey Mudd College to work on this problem as part of the Computer Science Clinic program. The Clinic program offers a team of undergraduates a chance to work on a real-world problem, while offering companies like WebTV Networks access to bright students who can pursue a project independently. I work less than an hour a week with the team as a liaison to help them understand the problem and our expectations. In return, they are responsible for managing themselves and delivering a finished product at the end of the school year.

The threshold system the clinic team is working on will store its data in the config tree. It will take advantage of inheritance to eliminate duplicated thresholds. It will have a GUI that can be used to look at and modify the suggested time-based threshold curves which are generated using historical data. Threshold violations that are discovered by the collector as it fetches the data will either spawn a shell script, or forward the alert to an alert management system via SNMP traps. The result of the project will be incorporated into the standard Cricket distribution later this year. It will be available under the same license as Cricket itself, the GNU Public License (GPL).

Of course, the real future for Cricket lies with all of the other folks who pick it up and use it. Because it is distributed in source form, and is protected by the GPL, Cricket users are guaranteed the right to hack on it however they see fit. If you need Cricket to do something it cannot already do, you can write the code and share it as contributed software or as a patch. I'm looking forward to hearing from Cricket users about how it's working for them, and seeing what features they need to add to make it work even better.

Availability

The Cricket homepage is at: http://cricket.sf.net/.

There's more information there, including the Cricket distribution, where to get the other things you need to make Cricket work, and what mailing lists you might want to join for help. People who want to get in touch with me personally can send e-mail to jra@users.sf.net.

Acknowledgments

Cricket would not have been written if WebTV Networks had never gotten so addicted to MRTG. So hats off to Tobias Oetiker for a great tool, and for coming through with RRD, a perfect encore. Lots of the ideas for how to improve Cricket came from Jeff Jensen, one of the network administrators at WebTV Networks. The WebTV Networks management lets me spend time on this project, and chose to make it freely available on the net, to boot. Laura de Leon reviewed this paper and helped me spot some unanswered questions. Thanks to you all for your help.

End Notes

1. Actually, this was before I arrived at WebTV Networks. Joe Balboa originally brought MRTG to our company.

2. More information about MRTG is available from http://www.mrtg.org.

3. Writing code is another issue entirely. As a toolsmith, my job is to write the tools WebTV employees need to do their work. Much as they might like to, the network administrators at WebTV Networks don't normally have the time to hack the Cricket code.

4. The RRD website is at: http://www.rrdtool.org/.

cricket-1.0.5/doc/neta-paper/paper.ps.gz0100644000114300000000000033360307047764266017034 0ustar bertdwheel6paper.ps{{H&O=OA{$ w{v.zjvvj: I,S -x#2EI$%|dKdd2/G_'Q2E]-]/|GȽ_Ws{z>؏ThuQϖ??j1l鿜\ї阊?glj:uP_./>dZue#Q4ߧb>jc۷Y.0"}mBr٢<؈tLs>xo|{Y^j!nD7Axqџ3 Vdq1ihF sbBS|A],_gO ƙeg_>'hdv_׋r~Va7rq%NNcx2Zե/z/Tc˃/}":&9 d23Bѹ_ھ`̏.._{j<ذon٢οߢzwK-?vhŸؽi?eؾa/]/عY//杨WŢq(Ϭ>?^(-=+_|u>mRץ#}hZc OٿsϾ⇿}^Cl0`qL{˪Ҟ !^`}]zYSi.?N. q+{wۋ]}@ ~}LKoZ]H'jxL˶Z_4oDT4\z19;__VČKV ^7/I<2U__41i7>^^͏狺>>%:!f>n&ot_׳Qg֏Ob Ii뿓CzAͣ@Zq$e㯎>~r7_;i|/ߏ璻uBJcS~^O3'#zt|v|~<9xv_9!U;u-TM}|sT=Yѯ)ᴊN džay>_|37&.)!;ƷfbƞjvV-VjE C=y<%E8;Ѭrd]?x'aGCHxL;1=>";w>ø6 [ۣb|JŤqAa&uGUIk $ "D͊)~ }۟h/VX7S@tTi"+xAk54~u?IoV2g_HKh) !/}lH'RIW[)fu,Ϗ(?v04l-Fn\Nj[ܧ]^?r7z^./2eݓ㧡/x| 2,( s2ɵ<$N ہw4ȢWf*WY^WRZLF{K| 5A\dx8$8}N9DlfA-OIDaRPYFMA/q{͡OK.i1 .A^^TFiG͞H} <h-) wii|ȼz}͵Vj@ !v[hѪkpxL]aAGaIp}ِR CqBx4s;]-|n5ՑoFIav3o.iXߋ|vEfɺ/`8\!9 I|KCd 9@4 ^ҀAtP~?͈UXd4C_0l&|KbT "@yNC[ Mʀ%bX}D3Zw3C -KPR]_PzOk T>TcT.ʇ!K2ω..h;i4xL'+Gg4Ǜ켞,A1 )|8| &W>GSXr1C^3:L i9|gܩ$ aTp 泳#Sv$( jg*'bt>YR@h}>OTYz욈~4_lNUIoZ鲞2Fdi<?(B"̞0ژ/yל6DL8_/XMXNmxD`ZOif|2:멬}dNӐA3j[n! iQI_'!xgObѡ0ui- ll!vY.:`S]Q?|gLc}3v:ے]@rpZs&P7 1$䅕$л^& %Zڤ.+В&:a>zReMD^m7ni,i$ˌ* ƓhP/+p!RC+Kb;sԇQpCJZ@lK'.F[OE;:ϷH.ϻk@dLĬ̂ހ cW.\FF< *c WPu y?etE*Ws!͢)mu5 &>B_S;ޓb~_~=a|X-i_8/T[VI:tIc.m-m؎aԤ24~F^ʖQǘhG;1 S:f))(~Qig=kr*̅Q?C/hSJv0c氛7dT6_4H+RUS]  Ӎa7>?+%ٗT.&ge0Eh.8 nBɫqx &34/iE< o#2nE 51'r%=DIe%D0RkE#?hd$Tq@'M+'&sQƛ#ke֭)Br@0Ղ"9)CHd]gn3;NiyB233od +y.]VA3&d'Fyx)UT`韂XTa,\ $f拋j4If>%r`sSi>Jz_lgy6 D쿩 uaY] J'R|5eIU$ӫǯϨ*9m$q%\) d`n>Hy,o/hۘ,DVX$O1ܩ 󶼈 +^1qC)b#(H9zjg#M+xZ{}8-)48R肨qHō=Bs٦-Fߟzq(zMjLUz^ gdymy^PΦldfס8OrL6-l(stɜ&)#I(@L\葑#Ph G˃ίkR"$k  v[6(>5_O*-[mDr>[giI$QnuDlyq!`+cp'Qć ]nad^V(ǁ.)G  Ǚ6h$Asn7ZIDS-`#Z8"IsaZ1B  9J}<1N5Nk[y'f}Ί _?6U[0:`j95Fr]+DkȴYP''3bfne7ZdYU+Fj]0q ILS>|w;USd \/F,R0DgD5I=dH2.4U`ҝf0tYՀfW%A:z4ծH=C\ 0ڶFJ M?hY3K)l:W+RCsDZU:Z3ښ+W9*$?̗\;L F55ˆ*cfexX xBie{si?0vy"xu$4) V `JP1 @&CHŞj ¼ޮow$h˛YB|ZIT{m6!BZ\KđH؆8l=1<H_c+ 55EiѼ&}QO/G@'DՍ:cLÙ$-]W"JzC_o<6ea(8VQuR4{ +52΃.%Y8r4bWTecQF9_QLYnU f"`c0H1ŝÜkX/R#Qj,$PU7N0=Wz?IVR`oy\2$|ÓbQXvˑz<}2Db#MbL|~8Z|COEF,GnS|BgdӢYnc=pGYQ(Qa ~P5k2sڢzSSvVEHw=vWteZn K#PL1Wq4x\d s<NIZ]eQIbK)I'-=@4̢`AHLUnhT(ux*<);jf@l]'bX %A@XMg qLT/8'Ƒdŷ̂1uCր P@gӯɲ ,ige+aμ( 6;!KZ/!^:';V=EPL6Zߋ/ɽsϬtnF+)L 4)-RԾ0kg#vXQi9qRi7 4.'XUXXKfA2`/<1Q^ MƎv{}Rx܈3>_F0"VqfXS[J,ahk{Biѡ,^rOF=CRj'_D)2S'52}t:$1BI%$Mv?[Jb^_)oxZ&<Ds?~BAjy0fD%tz|=buL$#_ H:- p/lt:Zݷ˃ '!O6< k\P= l,8e% Ǒ5N`%!8ZryO"3󐝐qh6m6C$YjB,]DYGW%gϱ1hي Bpdj9 X8✗7!yi1| fhdِߴ̖D0N[ KO)urv,q!dq2z3;"ՑTyg2܋i,(Z"oSH c3 (G0׭9Ϥ2Xa@,0ahU H2,G9emy̤{["?j8& >5*ecB4?݌s3T)pC%Z"$R Sk'rHqf׉;)Q)׳Hfq%DFT郤2f?@2*`tu>H``t"c?V0I0#O4nޚLdI]\`JL̕!P9zD)OmÁ E!F?IBsj"A s ~^-%1+'ؚhX?gWꖁ5C}E*ΌŀbsLCL0b^)9Hf~*KvU|v5v5 K!ؗ2*EzM+dv~UFbe HLy3RN.&Vog90Xn"/ʀ΍⨚YzJTB>ׁN0kT梐+"ψ@Dܹ`_ԗS8esfy\2ob#?joqv>oo?FSQJ,1c^x!(MK챢 RO43 < 4WA4!d% h % VdDΓɒG=MvzXDܦ$.ɫƲ/p<#K0g2 )^@bg ¾g6av"VeCtN켲QmFnd9"{F01Fn_ <"=0c(%^X#q*5ֺf@q#6Z-G``xuud Ⱦf&uWp(@_g 毆 HDpAJLXlla&5r5C(1uuϖ aͫ1V7 XȬ~Mڢ!Iel1wJ@8`FѩAD\ZcjM 1o'#0K,14Z$8 8[]szTcu/[:;><v R4(a BVK^I9+K]Ty\4GA+õpz4ڐ<ĽwxXUS PG1 11?!{լA-6-‚Id7^j\ dH[fA&J_FHhw~ڇ>LCK0m-{w~hPZ,Z=?.=5DeEl qZ}2~2.'؂O&Iiw?I,K>K>qd6dvd;3M)0mTiǃd\6 47(TP9ѷ~)OaI;i&mLk&q>'A'4FD#=bnL]Sۚ2 37uLev)\Ru2hmݬu/qD9/Ӟlb4/GKcHzٞMq=hL zir**  ].Ia!ϝu LalDM*o Z~Xm,09 vE::/DfzRwCсg$h03 :aqJV=}&}KcH]33JOirϰWkBfC{Xyа k iD.6!.M.EbL/JЍ[br9_sMzx`Yl&< b5DjiG\g O_O^6f)<N݈`[.n}PȲ}@[(젿BmV8( 4v^'qoda8Z݄Pbb>%c/Ҵ5(8ɲ`}lj֎! i fnÈ?%\Xh@j1]Q$OjmB+ܑl$y)xSӹ&R:_J L'i_øYKTe"Yq9"jo|ppHZV({ψ;'.3h8}-ec;0i%ك"z $lNLʖZ72z}cYGO#6{{ &',[S9|s\\ʥ;e.ͪҝ@KDWDt˕DDCʐcǻfmp~C1=GnHN ߤ/ln/\φ!5!=׸P0X:uI x p&~uEx$D6qQ 8%,#)ڜ%&$Gcl|KSXYp%,"؉ v42q|'<^%ifu)'MdJ'GBj%j91`ZW qE>j4刲\օ"rӋҌLH%/[s %7aCEZy1tP|f6t(}<ȱJ1B%$E %̖ ћ=^jnk.'qhb?| EX x:Ab%gc(`O'&Bkw~.{v#BکF(a 5wn#%gb$b#iY8mE;EE 7Sݬ?1.&$O$2+Xi$n36zxUő$KpleU樊=> #8g$`QyqcT9$.mfb2m)'r閿rab@9Ln̖~d/qw#ׇjlY Sq0s˟ Z,4t% ~58"jڂ4f*G5]-e,8kusiϰD ^RlOi:\@ZfߔQHY&ŝ?}+@2)נV@W1>^фsuO0~oOmDga;c1/-s4DI߰4mqæ=,V: |bXid@ү%0$OœՓVO^`Je쉂S\i`:Z:ѯ>6L5'J5V'b"?Zo$GPyDnk ,|ڲ8!XyiXJ5:`bG.|#j`~nLs\dF|,&Ts)`8s(ͬ_}nᨃD49Wg/ѫDɈc_@/P0qJ$2G:E^7Ԗ7!^iiLcnFʪkYXCNFEDۍK0Q%'#142)fE9ƺ1_E!I& %TR$x .%\lC1L-dè%Y`1)e\̄1}Sgh΍(j1|`&pA 0|j,q\/١H>BWY`ǹͩFۨ^֏VƆ8$~>ԛcYjbvQͲc[[KqgN|dW(^`p,1Cֆ)pVWA76AYlQA=2 Pcvsdź}]}Ff7b{Kpb] 1aqڝJS_-7QBkV/ 3c]y6yd cr'K[BBom`88AL$L@]2yg&zT#?H@~_-I=w9XI=w'mNZMS);C< T=Nf)[8yL 'G1eSeE% 6гYm̢58R5(j6+&LrXyxⴍ ukҪ 1(&5!-9|ުڔ(qbB3k^Qh0'l‘BvNV|!EDɸP#,űd1f崝SI25&l*7N)`IpZ GhJ3R>q+6qh]K 86 9 eW&RLC\va4DGgs_N%e\ QJraJd* V#Hr[j-gFG"HOi\Þ;?*9!&_oUJ$AvuXHX6 6Sa6 br̒ uRf:W3$X:t^߀VwS"!/Lq3D"ht\rP(~aAviA$CrKI#A$6N,HsB2p<8B-jdxis#4 .]gⅯm*(3-x'6ѵ6,oah{6M!k'>;;l"I0nœ6zA\U(`\Ѡ L'eCRZG!=W6Qpڪ $uHd`r@ Poq`Ot9)3nMۊ:h@5B_Uɩh;e'٤"Z2V>pFjXA8sH=R| '$ũȤI/h'GfH,N@ڍKQhhǔJfDHn<"%Wah|n8tbisCd.,0H+ wM8KbC Zg!$Jc 0N4R?KJ+ /"I\LNS3BVs)C$XNbWIdCJ^ vVhjr|L%~5k>ub /%9L' =9AM,'G_p@HA&ˁ` j ^uu( sIU & _aՒfmasɉ)pYw<;hY+uq_y6m:q6fj.zrօ]c;5`wJ̈́!4AR>80L@s}._M6I5, 'M==EƦHg{f%0vA#$P#?gE لc[OcqKY5Oyu" [e>3P|ṍg/%ciss4s=6kr qv/f&,&Lbp!pu$\@E8@M̉_rr2sq'pbzo?oLUR-j" zMUi,af?k"x2@ 2_&)$aW&~(cnC厳]r)n\&CY0_={N;g3WS+m8LʙD.RX[0L!Δ!%y\G!xs"y(&_9SV6) ̑ꤖ+cjv1MaMuV72ldrfG`2cL!27prz(n@ɆQ|8g֬騔#ډ>A"3GelX4@YpyXEۓ7TqL m%\]If*H%vZKd6ioWL Z٫1hWNb'GDzK7IK $T9bb0Xq§hGH8;zǶǏK8S>m}2~2utdd}2;sɬ< yddzw?gqd>Yt?Y^O)OO]/OahAyELðt;L#;m~lӨIG>i|l[Ov'uԑv'MI:.:.شK>K>K>in6-g$)1Vm9Il B58xG|6l$Te=,ӟ&,G]3Z ל.|s q6yaDeݑb/f5LíEF rW3e+NP#OXBe".'[!~-*=gq>,M1/|Z.^dq2>s$t1Wik1` $OcQ1Rm-?˗#7R{ԛc37A֡dT,K+?7!?&zf>MY@ljL(t:4z>";3#y^zy?syurL_a8^,ÌoUw.i9< U)GƼs`kV8o$p@bzqǢb2r%< 9q3 R'/ڬE [2P$6*.u4 6TsѸk!M[,cm}'ۛ(!Wx$"}© Ӊ񶹱Q722fZlİd\m@w1/s&b u7`>vBX1,#Xjq2!ɒ)<,6gP?kiYf_9) :o~QZ-/i`b 8Mˉ]ԒK=ؤ\-;NN_q#4=iLĽ!"8:B76qv/& ]ww, &kڄq\oi/5I/Ti yg}Ljq0 Xٌi"!#ٽc1ݔkS͡3^;q\Lؓ+Iov/Śqּ֧"Inu$Ћ%;GmTi33_lC!4o6.Q#$߁yeFg62O/kVr6ɀ|a#&IMȻvlƢvL WtH$RLXbWUYa]\*ھ\\h8Lm,gئ'N7pCrwZI{M"M{Vϙ`ύw!2& )Enj{>輺} ;1}#EhIV7r݉'6?YQ!P6hE TìiN4٥EF*6Gȯpu\ Liⅰ, Ķ!}[;^p29;kO_.8#}mAco#[Xy(NG\ V!ܶlc%0}guIR/>hIěMϴk5zqKL8ᕇbb `FΤeK8-g(o%@RrBѣ&hl|Ciɱ燍Pno-bM6mk#Xl/3e'Y76{=g֙`k4?a0?@8X{0,Y= _๤GAsR~˖Nl;2Ep b5MyEEfeshOEl&KeaMÕ6qQsMxgQVL%~3A8@_{N" Õ  ij?",dM<XR)So!@Ց- !41z@O$hfxH$eV1fuG/2^ XkEFYHm M` pl)W;.Ju!I"PX1F3E:|2n%UmQ)'r;/X]\` wc+oCŚՅ$1cT^NGLG%P#|+P~ȕ~dRc.Q-@#p, JbT2rsp8f˙Hbjh<66.H݀. `؀H.peF,{ i˛z)W&X B[ tD&8AخK669)Dm\nn"@-mN''_%7Yu4NXw,jm_@PkVH#7fENkΥxz FĜtͧu =l39}؈9-|7`d|sЎWmVYd˂N59fZr\r֭ʛ8\n\FR)akw#&m2< ~PPȘF( 8H+4&Xrq9l8,7Ή/\CwfB$FuV)M]%s:7,`k4F[B۔ﵹlϗvg[V٤u7[dB.pЀE"6(#k8xLkv1pY#b[{vf5薓Rp~ŵ8ܶl07r M^ϝN!qlЯ.>E`BKQj*T$ʛs"O9H<72j5hB"A>5)X׶CΥ&5i{f-cfr8x8Dw7 4f* -<.0e`f= !efYEmjŅ=wSig7QسymX)ɑKF >|BIк̝c8κx*{IvA&zf%6fZ8 r . N&u/Pm! ɕ ò7< #Ԅ$LunyjbPwvU1Cݬf<0ꢽͻ b{Ʒi }ḯJM $*+KחZMg7ke3e}u^7YK鐦ғhƒebIgǮtzԕNSW:bgtu@2W:]鬋y%+wKt.]ےҕ.tNKewJ=$\[MU[q%n.\n&>eڝMU[Y%n.\nx즊 t@\ݹLT+]v[2eꦪ-@37UT<\fnؕ:-\fݹTeu7Yw.37UYw]qQ&QDwA!l~ Ir|l R,ԗ o/jJm~WSӏ_W\db?!{ϯҾ& Ҥkrs??v$wŹ?VW~ ׊vj<ϖTz%{AtRƾ96/7ܩāiGS2Wk;|"lms\ŷɭžNW&[3B"<~C-火Log~=>j&M`>JgItqp jen3/l%r]UnoyCC_/ :aYJr;l4G֋~Lfcm\6.)>ڔǸ>I( tGOIS*qt!2`آ40vDɻ}wZ4/DK:mΈr.M=:nnq9|tr'HlGfZnANVX[{]-p&rqv0LrkڲR{z;=(:6E][] !n #h4)%| Ό{Cӥ1K~mȷ֔F۪[%'hͦ,/{8}{+VUnw:.P)6/Ngwq“m[nC5}[?MaFmHR$$@,tt-r3T,tKh}#; wGfީR+O]a~{Ř+lRz b5mwK3Ȯ/*N-f^(=i[jѺy-ډuNDI6D}giſf*_z^}7r_H Xdp 1s$*S_5d%qP0wlc_}7ɒF]S~v?l> E,L!~'n?l[OώYtN~q/_>AK_S-vX/H ]L^|vF-E--gBKij=!wf8Մ?k?X$[V-,cR~a5;LkQM];|v{>@vbN 1}~O{kCa4ӈu*0ʲe5V%4Uc ѵ[sۢ9휝w\9f;><-4oͩ7)|A~9ǣ5݀݊;U}K4ܶ߂;}Z@ Rs10MyΕ0Q}X06 mpwn0Q-7~ǜG*R f:\K Fayy&vPB#5 XFO<'xz9¼e dǐy Q]Z˨[@%uŤሯY7/vU#F]6Rw|@g+8]ɝr;vYkig/ǾRg񯃛y"ـ$\ѥ߷ +^9u6>jįM~ݾ [ͯې ;ȯWF Fع%j_xk2^R"-z'3\)|#2=?)| 8"_C4l.۔x Lzj!t޻?󯦓Kstj9)$S_R"d5iu£C*7gc̥7m<-vs>O-X >ZM?Q cbbpt`u-5Ж)Q7 >j /7n2IX~\3^ל==;7->%e]Oy0Fa8+lgGӬ^ϛN;%ekstzE>~6M]yww,+F KݭJ×H53fL#]7kQF=hq]ToϿrݲl#6Ҷw?k Lkv[` GYDžxA=kjhu3yiQ{Q|qx @foE$C!`kƇ-I˘4l 4I1 D,Sm$Br@3*q"1%f-Nhlj ю$MZ |Cb{KtXRl 5Kz&7[l&d6oFۥy$kio xWQ}] H1YSVI-C\"JOO?ԝ/'-Hk㖩ƿbpŠX11xv5i5Zz<<&k^v b4pZ-==͓hFlm9ӾUUaW_G|=1{$b:Gf:gC7?kxT9B^"ςWmqo4#3qzhg,@2埨ʙ?97K~f&o?~L}h8ͮ.xȠZ#-5{ l߷=_lf67/6ٙͦONؾ 7{>';{/lqMogg4;% X+qvӯKheCۭcԭs-_W^Fa&^3O}uJuxd'>6=r[*۞$[{h=/mg?[Ǚdɼ)G6^j0޷YŽw>ņO'l"OxCD>n VO%( t7yŒEXLq!Iޓf'؊ u>|c^kv.PE"WVֶS3j؟3SonMa"l!}aQ%`GF'&fWnV$U}Cllb,I2|1ggAj` Wl[ލf;r5Q:ծ4ԱWs@0L{aPt~tHö)oұva ZZHaOwc8~ |Ebb͙uQBץqOpjq6{?{og} )y>x,[s͘jD"S2niL* {m=B|LY2x9z=.^s/16<{%HWo<Oǭ [F6'7mZZ.8t Vifc`؉?_ϐc& A'#@^vJ6i2<ݴO|VX(Rc f}1rx#bKS)\ ͺg ͤ˾xQ2Ƈ?>C"_ߌ |.ED[j62Ya(8XJ[7AO<6 [d%ln0jg rd 6ܤPo'6@cVy0t2ɏcV.6nEo~rͼ#n^6vxn/=^= &նYY6jʹLҘdSKE3k@NoOT\%!-0@3ڹy뵡n{[z8ܤ蓖aƉ 3$|H:-88byA i JqXѧ]b h7È8QE)=hDDzQA+0Nk8$Mp 7ɴĴZepUKcdΉ ʊA%q;\k_Í lIo!I s3oQ4Š ucH 핵Sуk^c-!eI77dI[LB #uGQtmXֈ0*FQFKrF1waFѣgI"9=Deum0Z[}H9&M@]d:޼6HĵGMo=sУQ7f=zvۧ};ub筿ןZ_7崛)nH..E0gHY/$X9OM4eV h.T@dOIҭQt$$_õ] I:E8kd)m4%D06hR+ۛIۛu׆GynzTƬGn|o碿RW _v+Gm_vhңeE#w^ڬ[AO!e D:!G)dZYjO5ޟ1)mH$^SRI2.}C簈(d]L:ެ6(ĵGMo=sУR7f=vۧ~;b筿Wj_7>nE@ %] 1bgðO!4ڃ2,#a"$:GsK#S$}*-pH.2\.NEa{a$ x ڃa%.EtYwm }GynzTƬGn|e\q`^Kk(}ZnҞe7vEgHJ]w&3jkvg=')-e]K!9u,'M}nץ<]sCݑ=AlVʍ&/dZ/nS{V-%/fւKI(խ{wel( _OO~:x:[ qQ+_>w7& moiV Vg<+jn܁W૎r^aUmhWO܈}vj%ZiVVւtܫ_+|+z;~S+FѯUvjZe[e4ZeְSkVvFc_?qZUv^FץWQVgһ^5i#Fڧ3i6Τ}H;i#FڧCi6Τ}H;i#LzڧCi6m}H;i#Lzڧ3i6m}:i#Lz֧3Y6Τg}:i#F֧3Y6Τg}x >=%sZ 'a㧴>j$ͫf=nd7GTzɼ{&UjL̑wWån^+7k2i=;#\ۿ[\V䮻4cs9n千vMw~q1ls?ٜO.j8 M!mP1)yUǍ\ *WWܞwBǰ/oAAQE{rL!:[~K1(4mp7 ~ N^C:Vzq/%޹ 3kҔo-?Y+|X+I |Yxrz %P|.G MjTK{M^sHM'͒oHzTL\*pJV钤=&TsE%L(e}F:J<9&!s2CuMw_xꮝv^沚!%F{i-{q,7x} " b41L8y ֘\֣ FL9\;hci/VsB{=\brwNي&K|}x뢙v24kD_ς\ͷZ2i|ih6s+u1_,M|g'Fb7 D˘ƪl~hRh5D}ݲWGSb\cZ?J%;!iCW׎eb&3xx..;rwtX\]ӘLD Ӕ2!M=LJҒ<|*w^84Pzo )l|s \:C+ T:=1>L&S\>٘@lLb7"heSpdT'/:BRIJ*SO g,/o xP1D7/昫㠙َ^ÎFdbV\N%킳%|DohN|_41.1a W4,X.pg+zbBI?4efD?3(XNY4AIca9\I;K"T-{.¯.8#rrQ;Ӽm b_,&f]#W9-Cԍ03yt8$[$ E&cZTG#"Wb56©F綩|-}Qxĩo| EEyhvo]^ЖvJF1̻O(r#W:BǮtԖhónĕ؉+tKgwKt-9[:ܕκswK%y#*yf)Nb)Ncf**TEݩCג\n[MUܝ8-sni7Uqw.Ķ$e*e*et2vSw2vSw2]KsssIʧe*e*eҠFEo@$B͓xcÂoRwD|Z2eMO%1E;MX& .ދ"`EX7rRVd[scܧ^#H04(ꕥ SswTAlg<e-$iG_6,D.u=J6U͵P.bE8e6Hveq\ভ\7ݞ GLJv3 KK)u&o-Ɋ\L7V.n U$w=d7p-Kڷ~lv2W])!q,48kީAMܑ qS- Z ~Hm3n$iNXwan/p]Pk12=^er3!.iL=NLp=]]n;ˡ!n>-qUJBM,[kՆ aCѰ'zŸ8W:M1})6ؼE_Ywc b%Kiۓψ  i={)0Vo6K/K+ pm&Em01,Y# &6W8c`l{6ӷMÌCoC+jj7aݶvl]6{xcw)-l![kͥo]-IeVCwk@d2|ocq݆]D (f7]ry_)! W+f `|/n!޶e9ockJ9 ҿI}w~ھaY5I6+|#=?¦}%i#͢]bR!jXvö2$U +Kn|-T,NC"R;IÕ:)/Z|wmq[čW7a\Y|-(20ݗ/|0Y_r/MDZ_)fE)Ea&M 5]Ea,+"e= 7/ĭIL֗,)doZ_2(*;b1ݴdPjIgF& IDɚ%FK 5KʻOUF܀z_nc}YWp"Rk])i<=)-rOWޗ{Rz/d<5'ٚ/kcX{rrOgesbq&]kCr[%ؗ{zEvQ4t&V7n ;>7=bEؓ{:Ӳ.srѕ{R"${nwr xMT=)sܳNAH tZ rܓ|&Zxh'9⤧n{H}-krO*o=]OIqsm{ML=$hr[T,rOW,Mr>,Yrn /DlCcKqv =kBxrޅQ^B1MTv]"p^w22Zyg—-q]bo9PlѨA9ӏREyJWl{39 q=eJ3lVCF낕z%]엽;@땳T藍SqF~6ukdBFoٔnI7]L7ϻMkS7_v"jv]cғxl{‘^OܐuV rf™pf/YZ gn™+nO8sm g]1ʹ^I7]L7ϻMkS7`7Yzf?۔6gL@LNZ3[x g%=cGϱkt1ӕ3nϘi7fnj9w@igʴ+iiy7YݖivlI#M3#i{tU kпoL~@[~ګx3/_8i~=أі$#~L᪕$\T_u:Z\2"ܒ<XFTQg~} 䧶Ƈm2|7hΗ b#}9gM?t钳|K?cz]YS~]=gbxBZ 9÷КjF4nS'l{_3>6H?˶qGk{J{|w_^_~;ASneu/r'72r;{^y{ӫXn)k_kEZQVu^VĝZqVҩ brk[+J;JujZYVV_+ηhZEVgMU,]D}ˆ:I#Lzԧ3q6m}ڈ;i#Lzܧ3q6Τ}ڈ;i#Fܧ3q6Τ}ڈ;i#FܧCq6Τ}ڈ;i#LzܧCq6m}ڈ; `2klT+G݋9Stw<@I[=Lyh-\!_L=_$jYglG_2Y 4Jʚϥ063tʉ3ͩ=\>Z׶yJ;q^gQ57z߁߉ #f98]yƙ فqjD]5v`Læ%<_Ng!?5QO(&ٹӭDJSA|`Kղ|9}Т".q:5ZlkaK~g |gl${рD%2΢E/Ah1\g®i,\}ϫz:ſs Atgf2g.[qPחHV^aq/FU#jpuhiy ҐYx=Αdqg Ʋ&z푨v7H7wE}JB QC_җh&8h4"2vB V ;T,0k?qp, ?iiU/W'RMT՗2QXV1uhJ;-^7ʉҺP )pAt yRH.dbj|,O>3kqoI0>ӴrIChB:23.=OͲj z{nd!RhY HEJ^`CKjRZ 1O_6/-ͮI2X̉.X`]_9sa4ځb~QE.8Y9 I(b1$Ϻi"4#X,p$7sWZ*f`J> f/.V3|MԧMKAnեOλrCˎ!uO}GAu)C%6)1ϱ⚊Ƃ!d !7v/3aLqH^P h2vȍ .ˁ`p7#$!7a5zϛ-2fC0t nVrb,bb4`qS "״}kF`xV)'a+y^o\ ,+hi3TLBZL)!5R3Mȏ7.Ɵ}(3_2X([(ORo׆/<&nq-ZocuRJ6$4l6qhAlgp֐xVWl9\y\( ATr0,f8a98B]"_;$g=qz)oSzJҤZ]MӵS pgʇ|c0JiAr'+AH"Dp"D^Me h<!P<MDž 0 : /g_%^e,_2(?{Kְ ܓ}Qxz7ַ S(ft :=Dծ𸢅 }5á^Fb!kH2 llHBCy sIzz雍xô?@eYA ʖ"ԵCkC"b/l.33C~~5qM$h ]$g`&Opzj'fjjSQ;0%v~]9"?q*ZPB YKO|vA 1P!)FzX8gi\S4a;Je/K-8Uwhd>+?`,)$*ywvzXR#B:◠k`&xmc$(H5:gS#n/W DE`!HA 17!mkZe;61Tbv8X9"Lf粿Є6P|7< D:c0H'Z.gJī_4~eps$govҎ5QY=[ndi{s[X->\xX@oTgh&, cCH^lpdD2'Ƶ OA I}i0^Y S1!&>YȶXV,6C@(bP %>!kw}$x %:JueĄPI *@CoS0W #a:MٶO#[m#euBA %b\/eZi.E/aa2wp[vh\ #iBT/B]吠gˉUϬ=>|&.! TdL&|q9g)D`@#Rk/c:_-Y hH\: 4Fn7=id>!i╥`Pߍo<Ҟ<'$w&xz C#s  i0ٶMX*q_;J#sjZNi0V IE&8a#lVO/hG Q053M Wn.2~H*2=^cQȊFvtl5WPbz<𾞜RE9`Y*9Y"h  /㞬90]>IB=.r8_&Yҵ5YU34(Ǒ8M g%m"XvNs&f Dű>Iĺ^w#C;DD5ApFF:7llJ aM2/Xҋڂ,jׂ.WK .}eg74Aэr,DMN< lo|PIhr:_q[P 8KV;<۸6n*oMAhQ ilFk j7z\䀳r؜m6("9#yb:ocqa2H5DOϸz7-w/#!%{!&A?/&l"h&ur9$)xձaP;4b׺8&#bHM1@wlȀI(I3`:f>rPClx]bQ`Mpaip >3n0kv _ltqILmOXK1;!XI,d@2ݛDIw1md@{e }S31,=l@}^Ag C5"F~!1ek,!fD.f\&~uet]3~2D$0I.-c% 6@$&5hQUcEIо3dGYB1 KMK8n*28k&*9[p|-s9l.HL-)MTIQiIʧY>${2:+1/RBnjlXtsG+Jc]RxWMf35Bcj:MuNTFZ;;cjj;q֢1 )KlmL$Ed,a^Fae1AgYhf,ol Ըߥ+n989,aKWc 9yϨKH _ر93$\WVi9a^a9H$%Dl(u$"|3')l.ys6*SF!c&}j3Y&. 遨`b4cpx ͩFE⩡k;f^rߋw&$k2riְR.p,!f XA$Sp^`',VH4b#www 4 vbۖs#.#^!(&!b^;8urƈj%J|ÒrtzĪ҈ӝ S1K8^Ě]І! HpDرW?^Ȉi.L{eFzz!b')L &>d-~Lm]}*9 IG A3q,:kmuuUð\5r8hOqrgկdFH"]^\>Zrd/c]҄[+,fAg ֋9-<<9Lr`0n^;C[:79O$#.ƚf0mWΨ 3R^%n,G, y]ISXV mZv2p6ˋl2#''&O&%zI;m ''O 6brܵMfs `I8OAy_< (/8MvJfqC#Z(oj-'/>'%Og'rE5 H z!N,N_zВv,*%7gw1?A>&Ε3.P ȦlH TNXN/GX % &,`!}7r0 &q|=D%9]0,ՙ {%h')) ?P.a^~=͊t$,|8QKk$}boDNc,CKV*yؚ`2)M9k!fqrK ;[fy\MZNQRwaA|3mIh0M<l[m@36ci" ,{YΞ|J5 J_n:::+7B>縍rp̘`,M.027㴤„92j$g-NldO|=k0|oT1'1|XʹXcIh8 !&I\:T ODwq 6&Q<(Ass8cWN3#D,$:=LO9*%)ಶymʴg#'A-;A~iCK lN7}Qy_+] ._n#P8Yd|y~$ ` ̓zp avjS<(Rz!Wbh+fΌJjܳ>H0hjPE-}/Sf=~撃.="^Usm#xl͙D <ɂ٥ aA|2!y *x[:y„*qu≓`Ta_;OJ`}%"G0 >9MMIE(ָb\b %̎q:7JW Bz|a%o){/~x\,t߃`P>" M!N$ᦒGMe?=9L8.됳1M'R|>4gu|ah] հ!Gğxs_T 0/5o{8{Iϧם;0i)gݰ;K;=c ulןwOmVGYLϳ`vaw7hOm@ a`֧qܐ^wfꜛ .Jc6Yv*$ pE^4k$r˿I_$|_arv7_>yo ~!xXb6NbS 5mWe)lĞ16˓uEl5XD-s$rZN>xJ$ļ6PL3 œ+w 9Bω7O M,5KC 0oLcX5' _0 #!' L \|.jdALD"qbK4(5*Ob 4hPZːk9Յe)(0$h[7fX8y%w!yk_oϩZxs'U>`!/W4[ȊP8 "n׏Ã+ ƤȺAl/ I&2^A͓UFcZZSn!: 0Sp ,Rn:w"u")i_M$[] Q-'Ԁ f6'y"nlYXz&5ŏ?s^8Zr,\/htWP%Djƶ$kqEUs.l{'8ƥ}1cA@3;#Աbuvg<@; &"P g:bΰ$P.K1f:0ڐ*Ǧ6iČi@hoI !7WX _N0rrVj2A_ZNX0IOz9,WC9ઍH!1 ;@|Y Q]cO3ܤOW&Ƀ= 9؇M&bij^ueؠ@*B6X\Xcew䴻qҞ$?ox6Df68<$b31)*և28ȃnϸ; &Ɲ ;B$Ād]"TZNLI H0c8rqM9+($u@_ش8,$lM%G%5ps X-XBoc~ѥs0[<FGGB%a@| .;U{ت0Fh`M}Dg &ć3T HʹWݣy% ȋc"mC7ozтԒ]ۀJHlB> 3&jH7i._]ci?)2&AĀ OI8I v)FlC`fG2bI@srL?7_,+BY.0F!Y>gj$qiG)[WHԕxN-JRr88'9(wN\Q̦I bZvKd^S\]r%J3Rs2] F.򦡑O^T3* 5%z"Pm s ,MSt<6{1WDH34$J%Igp-Gd +PQ$1gJh{y`@ q"蔵Dmb9l | DVCSDL"I9{%m jO 9rz\<&vOW}N(:`rgr7'b1jp3<̤Y8ę$J1.9̰W$M6o9wȉ1|2sPL$]7uϫq*0~-Mm*JX뒗v G8L'դI/l&:9NWqHhb8̥Yvdg&ణ,uvC`QsI@+da6[S\XP+TB,Pm$[6y / iEd^;:ZX=rgCY4.H\IMڍ$o9ROL )&27IoyDtgpJP GCґ$xTb\UY$2^Yte&/ڦy "ϜYs^~d-l55p^dm$jD&-&͖8OhCmvO3>aA@rf yiA/#XL&!iH7ߤa[P.ƥ^@沒!O%59˅06R?+/S$^F&Bh+<F,%&91;_sWc!Q/d? LP26lyuAxZ]@Ր&&e<6e/$=qytCy j11&.zvz8H @̌H i+j٪}/ԙ.Ƿ6c]r{ LX9-[.ӺIgu^$|`js݁b|HPEoфG9Y IqS4ZXcjY"]f'fjw/lv}ל(ʏ\JrE< %]&/s'Nsa>O㖓㫫6ϐ4AƏ:Y,Ԛro5T27e' bL}մA&aiQrRby $&NO[F&/Ɨ'ej؝t_ s=Pìd229KpK㗊|(P f󊽫ͪ0֜.;6`>^N3& ,$׮\rnTfP 6ÅI%< p:mΆ!Աgg䲡ySwv4l4vTNg|`S9T%zAe=<|C8URGI>zAI2dC59x5s$wg#:/+eY/$ 0%L8IGmnbSGdgW?|g(22M)=~ %W"HDJdR"5%(+KaR"w% )EAJRp%J(](]!<.0s/󜯵 +!cG=@.vc˘nLc;H ~ՔRi,c1eԕz]4ݘ2vX.vcܫ؍i,c1eb7]4b7]41Mܘ&2v9s>nLči"c8:MdD.qc%NĎ%nLči"c1Md7IɽJܘ&2vD.qc%nLS;/ucإnLSԍi*c::MeT.ucʘnLSi*c'1ȩ]4Kݘ2vT.c2vT.ucإnLSԍi:^nLSԍi&c1eIY8t%Cxbg/<˂-ҟM3/zr֝_At/>&1>g`%Ϲh>l+n\3^1I}tUsi n.O VygYgAG%#j-xFi n K] k[ p 'J6ĝrUw.w,`| ]>k^R( 9{'"#}JKOwz 40v1xg>?צ  DBX[H.\y{+gyPHծRk~kvͮ|4{;Vʚ:&r~`wfsm 'Dw8ѭQcq#]rҝ]GĉܷK=N ƶrtX컷sLl<{IiѶ=TucQ$ ~ȡ0 CEFzz]9bq{owm jwWu>"nՂGJ. ;}:Px][q9ПgGz8j^~]n6B^ޮ"$x[x5w?Ԗx'#NGa{9tcQJB=Q=NN!{9&˂͝G}goKm+hь0; j8('h v9{џ]x\cg`;ҮIeE+Q#e߾poh%k/kޣۉh8(l0n/ B ~5<c'3ީL!u1m4<4{Z(y4Zxk^V<]Q@]ǒI2C+=J ǰDH'acɉdj뵰SMEϻgjͽ]e;d]bz(<oQFjEgLF{D{|t_ vW~4N-zhoκ;,dI:CB}PGnNM 4zjS1ߵyֹVE[%b|bꚋz-+<+ڿy}nE'1[0У7q3q},{{E<8գ@΃-;I7P /m&@:5}o+;K,7@\)o32*~ j^5 xm5'gl"I(k%wz nu( oG>BOSPPx<&M.#ɚV Ew?d.6YѺtg4W@@-o8gJ'1VwD>hQAh.HtBQxG4MPPPOVumvvm=!JNmEr 3X}68\HQm\fQĢMW)"[EQ7*AI»3;#kr,U.R:PPPP\t]e-8fLMF*I,@|]":Ḵ~=i 3#K]r݌EHb;㓛NtnH@@@@So#u}H'cWWq9):犼т񖤴wuϺ)(((( ; ҐkPPP :PD[є@@@` 5Gє@@@b^sYEN/:׀5= XxN٫f]d.ַh*((((]7X  nrڝIoDgKۯt ;0tc%MQE~\vnm<[?:1nn6UPPP 4˵(((rt 0XZ)((((~l_9 ? @@@AߘH_t ;'rކӨ q{(#E[t ǔn"_'}6B8HQ@@@%@(4 ~Vkъ4M΂(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((({DwW@@@>TP@k@@D: ^{)YPPPPPPPO 1PhM @cj@@@@@@@+H@ԢG4@@@@: @cj>imccR:PPPPOJm | Yׅhn(q @̾ xC6I(4mõTK=&۾~#&+RRVPPn5xEV|gЍ0%$PЫ@>V^|m[7` Y{ua+%ePPPO xӵuŻv)!)(u@-hFhKJ lk7\ IJiѶf{yom#E^.(!)( ,MԢ[@o;^B֍ck>r(((|M: aDaahͷ m^$DPP pw!eiTPPWFzZGc# nV6z|mAMi tC@@@cq@'і-8}o`ug!hMPPTkJnYwoh7"8 ;x6ڡ;;a6; <^]2ҝ[ESh*(('s,m+!2-viɾ2֖{mjJH DGc?xTGՃBsp Pj(h*((' SNV0.#/lfxNGє@@@A|>߹ES@@@'x *((hf_PTPPPPPPxWԢTPPPP 1PPw @-{4{-ЋxNHP2hx,h\((([`ll]̋\!hn-lJNVt1)| WԢxЭy\{r6no+Ν$Vk^xח5z[PP1hD -v rօ[ Z[K[ߦmmh] @3*Z4wpTE.hUmӅhh {JK<^FhIũoOqn΂2PP01hD țvWd͋vzv=Mk"] Ph*){PaH(;Apq޾ξWe( ^DaEȉckO.M`+֞vD8mtO(#hf_P~V#Yd'7Qx_4q{ϵmm}m?G>r6VmkRq-= E@@@(36VPmd:ESh*((*do΂((()Y.zdJESh*(('QBMVʶ6ny~/N((( S(kE Rw()/M= @vHI8Z: &@ No>uRh# )*ۥF;rN(((;>1ȶ9u4P\G?$Q JvmGZ.ң )f}nьt@@vۧCt@@@jth*((6n-* m[Anv;]vHmּ|W\ -+m4W l+m UvEh: ߵn +GD ʉ{ }.h0]L 'dؑ{}"٢##xY{Gۂmh2hcY@>lې~{8t>T#wg+7&};0۶hk+ν,h43IT3{wqIztChд 7*{|H֬}=] Y((gGq,-v[%(EU#*q1=HЌ4UŤ Q|fwz! Rn6G]'΢dۢmļ;hކfof<6wt9+(((((HT\@-zDS' *('q[*uhҁ((( dϷJCr٢,((({ Ymn[T0#mjAeo<(((N]WBM{+#HEDՊ޾Z4((|xAwh6 ')dI.&j<((( 6Nr>M;lw8n5;Ij# l%/xE4\0`-<( 6Okn|(?Q2z.]#%viG7NgVxEj|Mk{0WNzB}Sy罾[ݵ/< SȻ3w |<+'=z]㶤 .GgezH7rj? oʷ]놷C݅jok{sƻߛ.ܵOlq-[o κs!kP]? ɾ:]mK}Us'!BV֦/h&wf \MBmw7 mmUѮ\tSk3W{\*d%wN; \&!2ErKv1V! ɶ_4mHuTEwY4wIqK\ Y]zB7RP!}{;EB.ON^D=oGxyףVmeh !|tXBVrͶ~wD3${Zq`AsgGUt-hlW=?нQ\wn+i|0E&!k7xp};E2^(ݺE9޶]d1˛]dɮD}84d[;^wf[lRtsUxƷQBoQV>W}BV5GR] ߦ{>wF϶1M. %٢}{M;mdsm{ [{-o ڿK ?Dz0?xw;Sヴh4an |lk!+WR P]q+^S#ݵ/l#mgmvplk!k'h+ν}] ע]B xmȸ\>xgq&LEO`*lkZnXptwĻ 3^ؽ\[L>&0[ \-A>r.D.Mc==D4#=(J%ΒmVy[4S4D{M V69]+%tmsmwͼ`3P}SDw׍f}L vpq]ε686{GI}#mr] Ե( ?M=FNnL6dp%+m|(N,`0ӭ@>AL>Z ގƇ}3oKwC6j_w,wohjQx'G,IuO8-|J!+ewYq:dmƝMLW(((D\mcvmXhFP@@ȶ#eޚv @@@>.B̼=GVmۂ‹ԢTPP#)((([>.ո@-E@((((I'&h?^ӭ D:w0.A,ѻڢwڂl3F=;]ùX:7ZnYuw]GA7辠0@azO ?xAr̷-1oz׮ {ӝ]]/چm}/f9+Zu-Nr`[ZIؾۨw>iVVp5okzf{$[Af oĝdŽq+o "l"oWᱴYhvX՝s "#xVOЌ*"݅m#![Cl([;ξgo_>:Syܤ)aOpMםeJEs/wW({I*{-C%mu!֒LxYF$lm^G 2nF([W {2oHgCxBV^]Mwmٙs*hn-obk?SGh>Y v+uIx 6{RiK z,du=Ǣj# : [4((((((|zESh*((((((: M= 2ESh*((((((}@-zDS@@@@@@6XQԢG4@@@@@@̾ MPPPPPPPPPMh; NTNTPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP `#?PPPawS"yg]غ,蚒(((;$. 5)(((cwzyVPouD;l%dV=#*)+((ɎH[K*>rQ\mkm],v$PPP?Q-R6ʎ I>!+vwmO J@@iwZv2NyEz#>*PPP`yk#Wz'GdU^gPPP;d ,vG\[6ͭSIY@@;v1bz{vJglUJvoO!SIY@@];G[H&m\i1lۍ [y䕔@@@~Nֽ5ASQ@@@ tѺ(,PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP0}RY@@@@Toe%$PPP"Q+hg;*'*((|}Hqgr((( p\r(((\x+r(((*vt"oHQ@@OW j"Ľ"DPPPUnl.Av7(((pMN‹t@@@6hC8nE: 7vA #PP4oh: =}(((*^7@+ nкPyOz T~:V-tzi[BvaK,v{ Qwؓ:F^h:7'B }^~a׺o-HQLiip<%~LKVwa%~LKVZǴd%q{kzTc\kK֮,{M]ƒUݻ `Òog.Twc Y/' %+rn/lDe$TN 1MчхXQ=Ԡ{$IriS Ȣᧈ.I+#3^xef5ZTTW92w {WAw o,B4aMcMH[Mmݙ .r1mgjطƒ|s/#e,ZIPzNeI6|IoY0טbAŌ08t[Lek1ȮM_4$rsmNG[M$KuLФNG,ŷtX.*#7yY.Mueg۱&U%+KWt4|$' RTh1G,/CW/Q|{E[3ٮ8jIuѥ]@onIMG;%3||I j׋t4 Vd 7F_y+,]8OB=(k0NTHcI}!v6n.N.#x>7JT>4$-KHYQ3Qy14gP-ij_$v12Z6-X|fˋ\LATKE]zȡ}#ٔ&-lފ,wded V|d9xrmrXdES+_k!Gʋhqhr3M{r(|]0ŌkXuAk_I= %m2hD/@ vr;I uɗΣuGUmIVz=}e-lv,,ꢑu4cl4&KDB6'눀tdenB'XkeFk*f0PE74R3m%m _1nF?|/{8An޾j>/3{J`&}# nծs4!l 5Zq֝cʱ6AjK oN0wB߄b){Ys} AUwH $OwF/r;G70Ӕ5JS mӐA'S iLbj _HL]D}Oq.1+g]"^i9NnB/J_v dԉE-"K0MD2\=:I}Q19 nBT^kBJz(wFt Mry>]hDž4jʷ buŷ{ћǘDEi(fԧ#V;Pb0G#m HX8>1A TXd׌@V8?crv=pBAw'kr&꠮bbhYE;\jg}Nv-XecqX; czu {u&ɺN(g/uW,ɲ'_4uS iL )D.1N}7Hpn=vBȵ$#P#'&yHx/dd5= 2z>'ڧYl(G/ɷ0E@1xZ{WMUp ԒkV;0 aDyT.<5`[#$c/( a.2xQ/rEbG$kdA]z}dŢ7(:"YhOﳆd {GPR~diee@2yw fiXGNKS=71_3>;|@Q5f#@ 21G#O/ *.i V4^@R"C##64^{7x/\d$ 趽Mp7 kEO,_mb 5Yw.+dnMv璕+L_ɮM_#wRG$/#'H$cR0 f/*Cg-s(| ƏW8nWrGb ʮMj:Zqȝ@ծ-(>&ѓ~0(Y?0nڻxN(H͸_q%HfeʗZP;Y޵" e'6$MGT31fSȥ.yiibɵ#)aD9"]ƋI$/2/[7\dR5G\FI}mrՋ)e(9~20PQ$ɺ:"YU+²uE@/DE( BRd"wTNw2F 6X{ 4}YE@\j &r`tEDQo3 17SD&Ռ¶ɣyDZ'`n]1T2F $3SRHR dŚ dz22H"ύ0sF3l3(مc(mK, ~h=}N0)~5DP&K2hރ#*\Ȉ@Ǣ0,휵*Q(gNW2`̳Y]iʑ鞬ogP)E}ث1E.zd ȁMct򗈎#'U 1;ծHjqm:{mBXPWe.9Ɋױ^Q:W}߁j)ׂr\:yѤ$h.qXbmTF Y}rY–LC9m0oL֍UkAU^-6xa 5@./ˆM:Zj41"$Ph>]ٛ+>.WR3\d"dFNS$%PᢝOJGSEمK rkˮwj%Ft`! */: IReq-(E1#qXM [ H/*6 h}rA]i\\%YNc"_3ۄ\2Ra +2hA#@H֝ԝ0Plf#uEj [(<,dQ"c^QpIM +]'gJiq#u:΍XwBZHHOC9W"Ad6dHbͤE5c;XTir.dEǾp|a&ddF&%)~M>P) %U^:5izJY/Jdiee/rRd&YM|qyVc E=PpBZHv-[[0Ɯ+mWA"hDz{#+'vc50bzxw4niS00~wEj ?I_ydi~؄j *8(.ˋ&.TɋnuXdl=,|Mc֋cV/q{h @B vK;jZu"(@HnFRmsVOӍUqjtwˮ{ryF=|w"w;ލ ؕp&[QM(zi(1 v]jDYwv1uh= F5n tgF0edk ꑽ \#w8m*KA;4ȳPǖf*L[J]HpNgeUyl7V ȌqE&5F"X^by#jiQEM1 4h2ZVYYRl t2Q! byn' ImǺc#,20Fzt=>Ut3X^Yuc5@*-֭UGh&?gr7{E쵅;SZRG֧"Y qe]þ(v tDqzY$+_:*' )EvmRgge:Z-j3đ6Y9cލՒI #C/Gtcq,wEj {юWY\3njTݙW]ta1#YpQYjwMG2hqpQ+/2hH:#1:yv* 0i %f夒gX-9ĺuz,.Nj:-ˆr|]~#Eۖ$Pwa>e0X^ct\ 2Q!$Ɂ56\D;\d2΍ȳP^dRvjĪ r01ho}nHbݺ/GRh&/گӪ]OYY\wCGՌ]9F;bKW^ZcZW*ھj9\7A\Euծs\L_omr> gt"ލXC9`--mǷ6Q9[7VK՞w\D> #XȷtPm2q#keȲ&مft4Eqⰼ(uh.w&u:ZYd @ՎRe&/:yvj r41N {RZr u:ya 5`.ˆq|Y<1k GWmY](v+OGPdծ7A]zR_B%HeE2FtxvP]okVOӍՒ)Z./E0[E"k fv r7AR iL )NˋpqpFxvPDmʷYZr uiknb #z7Wk Fydq͸Yd#&5MˋnY @n$J'/!+KKG˔ )N d 5-F@LCqv6+'UNjɁ$֭E8ɀwȤE5|YծY[ ծg+kEd.Lߧ,W6) @nڵIMGSB%Hk|$.2F@GLCQɋ9+wEwc@"/#Ee48Vȸt(,F_Y5YwddwGCyB;Zٲ^]zlđ,22(wz"Ȥq{TQadz3T@ 4rKKɬwj([7VK+S%BE;AP&-|W[:_smcG׌cd.i[{R r7}u9LTHcar`eT"3f|76(>/ {W3 F1I/ܪ]ȟhMv1u3(ݹp߾w#s&~@h1Cnc{A>+WsHXJdd I/Q)# LĎ2m' Z3nMU?hX)2ڝj̿x+uٛ$ M,6\ҪWkA|_G#ޱZ,/2 R*w f6xog$YwnoǸ]ݍ@7MȤ#9iQ[@D@. ›[dRlU}1 "(0uzFxXZBeZԂA^^(Rh 6,:`߯b'L50PILi 9ɋ08sN֓Ġs ߄qB 4#QOuZy4a[x-F[5H&MU@3YJ^}ˮ1b$Kɝ8 7#igwج\v,Xn.* S7":f.>#E2KML)=}rVsgJ]s{uR3+^Q[.,C}nT;w۝F3lcY/R6hbQ]µK.gHJz")]- xw0Wt=|/f<˅v.\u, sbr6hLȶK8:W}=Q)m0 qwV"8VW+0DCaIz$=(9vcRw1֢1zgu&Un[7jpʸ$5w( 3>|uT$R$m/Ds֙]Qwn#0xujs֑]LSwIfc jaD`ŀ45FeXUr F|sHpFT"l_FÈ"@x #E ˋ0"r^v;45FVH#Ef. X-ˆIn#EQ /aD`c2F d"la@,lG/l `t5F|X?jFL@zW^5ň\A (kBɔ2FLE&/"#ޥL_ F_T|m] QoM( aD.2)~B3QOD&$Zs40I uwvL_ ”;ڍU_4Zc(*̔2Fh&/8/ 7(<2oRB`3PGs|jl2:nxeh$&9ܩMq8P M(ȤEGyޝ_`?&$iq@QhH`dY *(H=T~rH$I|'Kq(*`ǀ:X4FNҕd%5A K֬3gT(r25ͫ6!0u?"CX H=(5$(Nrj0UM3)5rlT"mj5#0vɹڗG "0%hR3y#Zٛ7 P}ԾLAy.N2O8ɀ!" x0"q/ˆ*/ˆa#Fq#[*{^pt~F?# n#RYꆋ00 aD`2FhXTft"EPb01)~Fd7b wȤ؈ZPޭ_xq1"FĈ;2[膋0 P&r_`9ٌ(0"FĈ#l0,_[F.0 ޙ\ry @&/ˆ]@^hKaF=1NaD-:N,Mۻ]cĢ4c9 |羘g1"F*xc e_黀؈%6@n@ d<-^H]cD9Ld/5|Q+,Oo#ӈpF^F3!j<2tZڀ [2xEH +ccuLj:D.&R[/H\ծ1FVr=eH -m&x(#$S*\5@/ˆ 2]:#D/ʮMj:ڢM(D7ga7$@B`b@{LjVec\7Q( M%7rYi1#Ȥ8kԎF>@ Gx?V^Ph&7 ^(Il@1%~Fd$Èe4瑨J }8EƵzTJѴ.hCؒ,:>u*lpd@ڤEB]cDZ8zF6a.ƈ;Re_ihO*HnJ3K_D7XZEn1՚n0*6AݢQ2b"!קv,T_L3N5Ѣ" em00\LF&/IEY82+pDב׋7v&&ZaD?a[ymٜ#h&/ڹD@B[7Ŷ:: ?8ÜKb9Q H}͈E,/g,FqEsb-5O./Grt"C#Huި4(x{_W/Y6ydRF>8#@]*K] 7> #E+&tLLu Qf1$78C!Z,-j}XJJsݫ"*=`u#H{GZ&P6e_DoF/ T p{2 r0 Ԙ>SwCz1#[LDtgGf_"K0g6')]FnZnăXʖ_FGh&/:bT~"( 9i\O$kF\O,q@5[D<t+_&5^"C#c6X6PpZɊFU@M^%*Y"K 溌9qFtLHHIz(F U'ET!:5O/GҪ]7%lu0|E18;jˆ@)T UR6#Fm;Zp9P\Չ:7,ym*NKG;` Ƒf-&yTd ;xg(wUFї$J?/EQS]LT!ˋF>$ᢔ@hlPDeӑhoY/Nr6wٍLj,FvU!'x,@u&7yaL b-$7.#qfub"/* $JRMREmd%{9#&H3@c$(kQjKԼ!qI ɋL_c#NR;C0cR&ZFgY$Rki8pUPgeש5 Tu34^D/*uOK;N ,$Iacjd]ab"we ,,Wh(&SaPM5+j׳-/5/]v HKG}t_=/RXG?kY}) Lmz(Z6ײo97y/ EvonvOv'aDHf[Ls4׹<_mT@=n&&xcPlPKmPKJ1,,#zF*V7#M9\q 2w.ȍVNܨJݟo$zv\B-Z.f7b-|E)F<)YWGb:Uo}ЍW [2x.N2 fRvl2՝Xf+k ^FL;[(y"و!t֥kkFbTȒ/JX$/X(Z#jEHNNvmRvf앾UqYڈTٕ0+eM =ňQ5P=zP^.bݘxr$f/ft4|@JK[eHuqJ $#LVLD,5xQb"יng8yE$g@g;وNI#1mܢl'FnI +(H.Jinh&>қ[|M$iݜJL N?Z1"{KT@0~uTaB[ɉ~QXr"@j0\&>O}DT}LlS3&?]1s0]1Lш: ,bzv#1FyiDь'aD#<;R: ,E%UwJR;YHRy6(Cn m2/{ߍu] ;{8 j:W/Lnz}F,7CY u3񖦿#ֹ{A ]i2v}&MIˋַGb&/LחIMG|?8V=mN Ho1ʈjב(O7#W uFTˮ5Di؊ts֑86q NiPW^vsRH⵶l״)Xɍ}sbrEsF3MHјdDfgeXNGRE}#V)7IO7O&x@g/}|7ȤEAiR\.NZ;U:GoHmbN6izEh:Y ,NPOxHuzYݢuXŋrD#/Zw4q:ZEG -aD5MG5v?qemSVKruKWV=9ArD/ZE<=-[i5S/MbİkR"\$ #Dߓ*4^K{1#|D򢘰R 'Ȣ.2~$Pzk`F]`uڏkEB (1b2 @&/2)>8?KuxQ<r]u͈r*mFJɲˉR8GbRIV=HE?ͪX/!WkzcR_u z)W>;i~g{={OIdgu jZ Fֈ[?{8ˆWh^#12w5kRʵs5*d эX}ਵr`a])YIqɥ4ѻ $׮h*9dhT5Q5a 1onQֽ &$m=,<5bثQiOj׸8ɀwȤF4/SL#/ZE$uҊ1)w&]6'YfG6b-emTTվ)nqXZ^`):[UY,ҜDos`n|BhDU~?M+#pQHn\JwnR{gSތ7ZYJVg2{ٻ19 )4ʤ6ENd,7}1K}2G+j=1]/=%14enlHE8ɀ2FBQMڈ07)~KZ61`H ƌruU#QcjMu0Ǝþ3."zZuzL+3pۛ0'd9<@ت}F/YH#ԗ/u=6Yv U6E2b9oc٫V;J"#~vV$F׊ y#j4Q4i,jD_4Tʈ}83%#Q4.r&Ӥ[2M]#I>R)L@>`Bx*kLm#Ga{(u$OcDp)TuF Ƌ2=tFbO^`Iŋ#7YY"勵H3&$OwĤy5w&*Gp͘<_hX3HywuX#&SAԈF3?ֈֈS"ǜnB=<>PpL&&  E&/:<jʃg0 iE`*\&c4FT1c0#&j1u*n{1#׽3GMbׂr;]6Nh91x9#s3?JEqqe4II;\y,&Uu XwRE"E*j#(FXJ]_6茪`7g%/VĬgT$)y^~*-mt%UEI%YU 8ʗѤ#ryF|iI,IjJm |y3iLZkDiDkTԲ63J|zL (=R% H0%}gľ_*59> J.DZneH.6)~w/@v <-Wp̒QX$h&4SSmӝ?H-g2O&SF|E(H˖6H7zh8Ru.s^ &G.4rU|g0Z7=RTt5>ҫ:yV+8/ THbO,M(]/hGQOۻ\j,#ѻިr8V5bXX?T ^ԩ>7ki.gGŋjmJ^膋L_t@#c`9݃8`g zzQ53--Qn=j߾:Qr=SZ|:̈/N2'fڿYY+.EHZv~**84_:& $ڟqJsU,Ab"<\DA)6nYr~# WR hF,YY$Ͳ-k*&r6uqDf>#:M 0h0R[I;'Y圢z;ƌiPˮKgF,+&D~(^b"G*k4;uDg L3J"wGzs̘]@jrli*hڷMY+c3U~"FRF|wL-(H4^ ARdq{RkLȈunо1Uٛ2]@mѤo&ˆE˙aNA|u .P Gh&/:Ŋ$Ud Yڨ+k34:3oBh3/#1i'A(' u5[Ոe]"7D d%q}U2"@ф_YI;'YE@'/GT#I+Wdn~NGSLěQn#j+8gd F-d9~9⦤uRre_z#vw&_#O/EXoĢ `@&lrH DdJ]cD9"4<M^Ld|`68F/d9"THEnmpw.ˆmG?S*lr@H^dlymGė0b0Ȩy1)47iqMi4i3+nmrw'@,/ISvd=$U 6X9fRѬ3H/PyzIw#V"tD6nuTd{2bQaĵDȤev#Ov#]6X;@-VlAdL_:"`.ˆq+]Sֆ@q^#0ZFbR:ZۂRѸe4N biNG7ύL ӤE3"ˋvlčQ펶b"Td1^vgpE^#+Lj:>VڤE0{2z*O454U5iq!@74}W~[[. Hon QwKK^~ʽ}pLjJL>pWlĈA FĈɻ`5DȤezzItE&ш_:ʋk$ˮGqks2F6^LD%/ 0JrIVԞI\M-Eu6zcHV11ʼn3e&/ҊyIlPC bŴ'phc@/-x9m](@&3R* URgù&YRɉ$ sI4I%e P<^(u{/cR'dZ@8|XJSUz;K"WPƾeRD`Z_TR 3o9&31h)02"-N?9F ,ͪW1fnP9=Rp7[*:Yte+dIRזb(KJm>Keb'K_u~#ؚZ+E0(IqXIRVJK-Mr](}'c@rAR"ݼRb$D0"@qe#P xklHT&,b)GNT.~ZϖsejS|ڦNPXځTj%W#& y52e?#vPRrԃ8KBf)h|A)t5Du8Y\`%Ж" !&Z[Em0el">h"\`K$my7=;dw]uJ!K7%eGF@)#ݶZ#UWCSI Y<_$u4c^r5UJE_ ԚmC enC U~&ʬ+jbII^eeۻZSuUDkMź"G7fH`QR۲H*)fRIkr< #HG 5Pbq?FWZ_a6(%~Q0]JF"2@Y*mJXJF""@Jϥ^zQe +gPJI } \ KSj HSVyJ-_dwR½Ku4Z8QLtsg_#Q&CXs;}$߁e(c֛jcMR^[PFTD08JpLd8cIaѿ0XԈ e`!ɵ8" ”)ފxaⵧIDE2#EPV @$IbPJ#Y0#LGsy-eKS v e@A:ĸhH#=Ze$Uio7*Tɾ`>;y&O $ejSedɃu* WTkڢEB8o6Íy(n 5!JM\`кdVk>jwYDZdJ܍Q FK!N0y.rֶIl ٤E%$ח"`QDVuԛŖ1UZ@dRL์+c ,R{f4{x{9j@Lg{֕.튟zsd.g0LE>$[ҪVJlP d]Vr4r ,(q2~5N,4۪EٶO?`$m;&{4*.g`7 %FʏEY}ĈQaDQYbD7>bDZ#>8;%|^uѾLA1gL/#Ц&ˍфsh8;cu} "d"dR˒ h*9lp6C9[P ߌ"nV 9Hr@뿧kfчEml nCSlUr}B*byb.;]~rq:Xx@A>٤=({ =MUGObʗqoRkqw7Z 20˳ro$qq>yOF85yfo=۽ysLPq&&1o)>+?D_FZP^{zJ )oJsoDugř0g`u\+cr FR쿿Vny<6m~Cyw7ྏd`zTל׳p:_pPzkJK;.N͸gnѼSG;Ƚܩ6HW[ݠKczrŐ>Db/$D߿9Xܹz=9;}'^ՇfADgX. ^3ߜ#1.@*uw.PV (:rvg[t?_ߦ@\[#"hp@˹qS:ΈK񋾟eՑ:~4nэ;Ϻͧ|=]ovFFtg@ո2Az_ IRivHN6\q788XBQ,JY?Q`\xh^m#lhƔKX(%I׿jrt$J⩃I_l4|0j ;S}E=9QaE΅|V}0opELg B j)$E/EV[En0Om}:^Gk4ؾ7D)2""nk@QAӲ s]s3?(s Z7T6iR N,'@ӅnԤ~t(GT>dHm>je{,:q^y(p '?7&E3艚{z/GE:NV/zн?X`| UOo+}}/rk_qvhלD/S2XxՁ(_3ڻFR4eiR3 EG`$3% &54*%!yuUNaC~".Ut .>CUtea 9Ҫ >Ϸurl0/@:NViCeH߽~'hGU]VvƑa2L>u )FʭFqta"R#%JI >x3Է1F_,:b@yי2*a[8{f]'z2+iUڧ0L8v.؛ VٞxϞAkVrR{Ƒ:g`N^3i3˫2c ~ ƽ*ws?U^x6lqR7 $9p.LK+Sn@Υ/,=R˘ ʜ lF:L_}L֐=0,.tz(}.v)ݫWho bUrnf0_vUg}__{^ڷWaHតX߿'zq^^Ջ^oݧ˳>F# DDމf)RP7Avc5\ƣ9DnИ.]0΁=8FH}8V! a/,l۩yx(U._wWz2ߨ|O`E~LoۯqW꡽x훟t|ٚxmqh>J#Q%OHbIaޥR&qp1ŴJ-$ K -basJx(Ftki9惰b;|P&,V`Hؑ{؝=nU#;]w>}SeC7dƉu  F` VȮb|?Z}3,^J_ \C`/GpMb{HJ;ِOCw~?@3 mz({L_>Yj}tE?fcE3]JwHY]0.={/쌲ܢ^}@ָ0n*;+?dצ߄ڸC&7w&QjVɿ'YF^eﮐ E>{ė%WkZ*:/ 3 Q߼9mEgNsz˝U =^}[vwnrkD`5 =~$;5<3}}V^ Ǿ_n~Q}8w/.~q}崍TjcFb 4A5yUFHڢz/ޱ"Fl_h{ 83 ae{G߾_8=wvsnbO7ȽG{u=wt"zC5S9$xQgw4gҮ 7ʎNjP-'muPQ`dhC9+B?gYS:KVh:/`m: zOA/J V0_Fڛ~B5-I8-_E(@b}RE:\ay jכʯukWz up-5deK9J@u9j/N  &4mwYy?KŤE5,!wXtAvG`}[ʃ1ݼ~wڕgdEׂ^運w/\z&^x9Wwz=Cuݪֿf@v)PΨW(q]>85U?254<-ʉeJY(B8b(1.^JY.}L^: Hʢh}E?5 EkV.:&wwΦ$#[E}3\dfct6Xhh0dzjvkʯ^wtǞȹ7KݽNjk5k-T <\gʾ:M#w(浩C *9 T~V Xh.;@h>xˮ39\.+3V\zx#=t+rDX\]{FS7 \1v,}ϡy]:~c z[*k;γ+`ki*ףɊ$juP3FNTj|2e+h>gIF9.:XvNHz6 7a7\QkAGc{nvnfG*aWXL𑢫}4bEu 8850NF>榠~י|A~ogտ};LdܒpǑI{@nsȣ|poV3fe|_R{Mo];>9dE t/t"MB<dXdCGÄnkZ}?a_`ɍ_#; r),*mu'N>4h;/ט/߳ KJ2oLGJ]y v?Eu>|:ןw<ۧo>ڇo?}yrz>Ox~8ן>Ͽ/~{>~Wݗ?4rW},?.ןʲ2t*^TOݣj먺{Tc}W}Fct:[G5:C쏽buuRtNuԥ{ﺳ{ԝu]{ָUQ!^T|}UwlTVWݱQYcꎍUwlTVWݱQY^uFeuz56بQuFeuzUwlTVWݱQYcꎍUwlTVWݱQY^uFmuz56بQwFmuzuwlVݱQ[cuwlVݱQ[^wFmuz56بQwFmuzuwlVݱQ[cuwlVݱQ[^wFcuz56htFcuzMwl4V7ݱXc鎍Mwl4V7ݱXtFcuz56htFcuzMwl4V7ݱXc鎍Mwl4V7ݱXt;6/nп<{ۿ _۟u+UUeUݗ/7￶uUU۟uUe uUeeubeuUݗ/OߺuUe uUee}˗o]U}Yߺ&}}YߺuUm uUme}˗{_}YߺuUm uUme}˗_}YߺuUm uUceswHcesUݗ/OߺuUcM uUces66v_6jln]}Uݗͭ/}ܺ|_}wg|q_o}?_ohߟ~~zZ/O?~}j}T=y>r/O?>}}A~O/gr&'v/{_闇O|?}rPğ={9O?×~g}6o_3/>k_{n_PN/Nw/NNwUT>}7 ˹pNx7է5?z9_Oۯ_?G_~9}o߽4_^[f//K?No>ߝ|~M#9OJ|Ͽ8ս_뗇}^M9)=}/Nk?>r+O ~ַ&?y\4|}Sn{חjXm~ﴷ/O.oO__>~WIFx=ٷC8}зOߎCjꗐOSSs?k_z;_/tƃy]Vc^L {NAӅ^. uaDq"Fk^Mr0"wNFX QD]y\XJm\MAD؀pݜ틺.]A S@:s1Ap郈f#llFԕpq@y'(hh{&L gPnB{`&٘{ٹ\Uɛp^U)iH1h{Mh&oy;SոqQ%ra44 8c8 j$nhvE]KޘMU^Eh|߄i{93Mh$FL&݄F:gc }XUp^Ǘ⅛1SĺL݄s;S3kѢ.kAs~&\ziM7az:g}^y]MhVWK;g35\U`QKyEˌ9xR>b$Im;!wvv>r ێKx8hQuxg'˅ӆ. . Q.D]u!Bԅ+:]8Yx.D]%~٢$Zw=8o Bm-:gp6Y]p^_v hbS:u!.S(wBԅ Q.D]u!Bԅ QuaDuE.D]T*E.*uQp E.D] #@ԅ Q.D]u!BԅkX$B5N.y 4Q.D]u!Bԅ Q.D]uQF QWc;t}'Khbz7a^( *<pzz\L4H#Bԅ QuYu!Bԅ Q.D]u!Bԅ 0"T :E.*uQJ]TRN+u_UbED] }Q.D]uAԅkB]E]}%} Q. s@+Y_\u!BEQ.D];!Bԅ S+54e\ꭟ߄{aXo}}UWXiv읩1E(},҅ Q. J]u!Bԅ Q.D]u57 D]uQD]E.*uQJ]TRS Q.D]@Q.D]u!Bԅ QDQquD]uE]=u!Bԅ Q.uuy D]uQD] /vSo&L - z&p?&;xFtdn~5.ؒK,uI+u92\/ʉ Q.D] ޝu!JݢQהVt`e.ݙnDԅk P8k"QWJ]zFLN&8Z5 yFҒH~D]IsD]#8D)D]ueuiO߹P庥3pu *Bw8tPw}QWkgu +REԵ@ Wfѡ RF6-ꊍ$QWP8[uS7ŘTKtg~~B{kzS50RLބ"},] hml"730 X ٚzJǁ҆/gqLM@u.[ Qז D*F8Yx{𕴉E;FVksIVڶ(ԅ+"Jވk7.|+cIQ;{.く$zW6GuuQWJ]y_|vFԵK$,rtyY va,ym;uio`MuEfuk`L"RRQN+u9(wNt='*uom٣8HWE;5TUMQ-qK;H롴`}@gRJu] uM^[Q6МC ƼQz/c},RUhR ;HMg(LE]D] %D]QJ'XD]&HtDqDA$pKh1"j].q &tn~QWܛX*.D]#*uyJ]u% u-%~vn5NdE"Rt=lqun.qy"Pj=ZIh+5"._ܕKQM눺FFq|jr4ZzcY|j{]LM"kuOZibuuK VJEQxX\(zq$./hDe挥Iu/y~qMJǪF*QD9KAM;ZVo]ugjOosL&R)4Lv)%]$}d]Zt*SDʙumOUHRO/fk˚U--Km 8y-MX|FKuz:0iMKz- UQcHlHI8޼&rg/ }fN*u}Y뭷*uirHՅŵ&kBJ]¶*uE,:.IiJ]Ž%QQ[uIu!BԅkGv{MhbmD]umE-s Q΍ M!pp?KԶ.us@QW !BԅkUl9^;R ꒎ Br D]u:["Bԥ4/r>T͍v.=mSEMuƷ'jCcuWԵrbDhhG@|@Fx@I6[w[4ZvHM5&477 fv&6z~v3ݙ_\z%\(Z_Dԅ+u uf'kmj /`Hԥ!Bԅ Q9s' QE]~rr덗n QuŜ@ԅkH5}Rר&PJ]TZ( mR uTR u%)uQ^oD]u~q.ɨDԅ Q.D] cTev ;+*`@D]vG5R(-D]u!B5+㉺Ɂu!@VQW[Z38k/.hzwc˒@|@hν?k6W ml ИXzgm5 g7A:'ZvWc; z|]LM4ň*]u!@XEhZQW!\_DukǶWiˬeވk+ɁA uPwu$ Q7V Q.YA3bQ.D]&u|鉈&uy퉺~آ.36 QWVQjy V}QWX ԵJ]w7( QJ]TRJ]HRWR J]J]ŕWꢈ.S+ϴQ.*u!)ꊖ[,-> QQD]cD].E+]nCu2TB幰fY_^>cTQ,u!Z+88P҉ Q.D](h/?D]kuQT'kBQW t!ڒKugBԵ4`PXzMUބx~k뭯 GKƷcbB"!R.QYV A+xHVD]ulkJQOj|RJ9u!Bԅ Ql ;!J9}nCD]uDu:]T:ThF)o\S[,RuJ]EAWj ]b..D]YCa4u!BԥGP QQD]3j}F:!Bԅ QClRk*adk3#} Q!E]^RWE3E3,mxQDMh}4.D]u!:ؾk}A'"\]Ԅ$u!Bԅ+?["\.T8;^Hc9ג7|jJ0y/&1Hzgm5 5F~b ک Ml$^_닗k͢.عQ_"*5^57Q.D]-] Q2^3MzixjdQב.*uQk6KREѕFE._D;uэv-bED]u!Bԅ QmxBuuu z/D]u!Bԅ Q.@\ ^F!D]01".D]uMhz{Ϸwb"9}C}&47H}@#}q"ׄ{4n hƛ Z7;BԵ$ Dԡ0D],}9Cb.D]umT\h'UD]uej iLe,Z'K=h Q.D]zrE i |S.D]u-=/F;6J=uJ] -4ā䱴&Ğ@*uQJ]v(R;"p0Q ZZ # .D]O!ڴ˷x/\ K4WD]biu z:fQSEԅkPD][D)F丮"5x&E]^F3} nLI׺ 'k;h,MEԕ1Eå($JC5Y ތum%ArQ׽pXFerb+-AjG=N抨 Qd:ȉBS&?K ]\ Ƶ(){W=0c%@|@p.tWX2>[ Ľp zKmB&4cc z@F~Wo}{SJŷ앁ҍ}cAԅ+;at> 16Jԥˉv8م&~[7e니u#$t2~/ DM!:+93Y5 9¹PuE {1= u, rxm(Iuu!B5#8K`!UjiyٛuESu>AQcP/ R4FΩm۪ԕ Kv8J] 7L@P&*|+^G/pU;nA75c7WkMQM*b D]8Y Y uE^QW^@ԅ Qr<uFZD])JH3(u-߄D8H""`@"F7aJ]JzJ]TRJ]m!LOTU*u/*u!ڠ+wO$~E] iqLjnQ}&kjp^@ԅ QӅ+g,-҈|ID]v"# HZ QE]{ "Bԅ QO5"(ܢ;.D];uQD]D] !%yMk뭯 ʥ\L{]LM Q. ZT"ښK}u!Bԅ Q5\>.D]u!̢.q q QW:J]mE.*uV4Ew&*u!BD]+uэu!Bԅ Q.D]u!ZbuD]u9E]wF Q.D]"BuY/D] #$ZyM\.T7&L\nyUM9]O5srK62Eɝt0D] \uަsv{ >y#<&L h>k`5Mh&oyuM迷W@4sݙɒu[#<ȵC m 7a^8kGbKy6S7&4 Hg0c$MhnyUMh7F{gjsʘXWp u!Fui8|q Ap郈f#ll ue\.{8.kH D]u[..@ ږ2?A&uzIo m̘AHh)m]F b $ R;zJK].Wc 2k5M`\kH\̘AT?ׂ"3cv42k /K)M;W.`@4'].B llXwdt \}^ZWkmT¢6b Zv^dۉxfRg"锶t13Qʔp5iza %`0#P`T6`P3˅RPs0$ώƤAhP5 6A5NiZDB <3z&H/+һ&AzfF`$-ZnCԥԣqۢ.ڻbJD1bL4锶jj=8%.P$-&FD]&Q@h*" ErDƝDׂ8k f80V/T%@D]&U QR4hc@iw5<5-D6DpRM336TSچRhԙH @0Q D]3u@0Q D]3u@0Q E/lZ_ {x}ĻJӈ^63^WnIk!yCbM0jQ2}Kplψћe{GKt]hz rC”[hE]N3%3]ޘ+4va,zfݼ$bN85ՍQ@wfbc=`0.E]E!#a.c#YR@0ϩ :]&TG"#PJ$K P$)2@bN7yޝzWAg , v&hQF\Kl$&} u.mb3OOOSq*?=o8?O?קSϿ+|wey~ϧ?U<|>?=zo_/O_??};󟞉~~KϿ}zzׯO_OۇߞTwO˯`OO__oǟӷyjO?|?SY~8MJ*ө OGc/:XGj!^tjG:ӸXGG].?'ݣеƝu]{먻w[ָUQ|}UYXeu?U;6*klTݱQY^uFeuzUwlTبcFUwlTVWݱQY^uFe;6*klTݱQY^uFeuzUwlTبcFuwlVݱQ[^wFm;6jklݱQ[^wFmuzuwlبcFuwlVݱQ[^wFm;6jklݱQ[^wFmuzuwlبcFMwl4V7ݱXtFc;6kl4ݱXtFcuzMwl4hcFMwl4V7ݱXtFc;6kl4ݱXtFcuzMwl4hcl;6^=7\9t>u7{TmUwjϝhQg먦]wpS]s.ݣ>XG}~םeݣG[u׵ƽu}WsOcWQV?>;6*ӫبQuFe;6*ӫبNc:ꎍUwlTبc:ꎍ;6*ӫبQuFe;6*ӫبNc:ꎍuwlبc:;6jبQwFm;6jبNc:uwlبc:;6jبQwFm;6jبNc:Mwl4hc:鎍;6ӛhtFc;6ӛhNoc:鎍Mwl4hc:鎍;6ӛhtFc;6ӛhNocluzOw|9py3_ꇢN?~5pV?<ӷ>u_o_ǟ>UϿ:ϿӏO___~˿ټ ^c~s駿?>}9}N?r?r~ǧO?}~z<|뗯k6xxgӝ~yG}Oo>}ͼdzl>\\#/?^^/|h/O_wS}k ??=|ŀiǟ/ڇ5􇏿~T{?>ũ_H^{_w֧b󷧯/z?ِO}7ɾӗ}v-WSDW|zϟڿ>ޯ?3/];ݙ?_߿zq35`Z( .@{. .Ӆ&B01Q@l3k[6XLJAD؀pݜ튺.}%%e{iK|Ɏҙ) 36HD 7a0 d G"\^|6Q&4ځԤ^&4ڡ3fm M8kƌ9ޙ3ׄi{<)jܑdCgpvػ~pqyM`݀369H'Ҁ*M r,j.D]u!Bԅ Q.D]D]G̅ Q.  +Cԅ Q.D]u!BԅJ]T0"T :u!Bԅ Q.D]2UbED] _lu!Bԅ Q.D]uu .D] D]lwe"R.WM\_5 kS(kƝJP=OyMh&o™ ` -j N@u!.T y Q.D]u!Bԅ+ON Q.D]@RE.*uQJ]T /"B3u@ԅkB@Bԅ Q.D]u!B5 B Bԕru!Bԅ Q.D]BX'KhTuE `iGl`&\ Q.D]u!Bԅ+` Bԅ @Ե Q|!Bԅ Q.D]+r uexQ ЕJzD]D]/D]u!ŹM(:.D]W\A5+I܅ .@ĂQ,>vyD]^u!:K4ryu!BԵQN +]L̉xhDԵ'WD],6> pTQWI#Qd]$NWMt!Bԅk.u9]QW0ҥ-k QLӵSQ.F|p^~oCOxX sp;v{##*ҷ8Q.D]uel닍kɺ R@ Bԅ+W@b.Bxi~5Ph4%Nֽ %MAbBڞyG.sIYj/i0u!BuQ/uSE]іAԕ9]]RDbS[4F.œ4bZP3FlbRAD]~Q.D]u|F/D]uc;D]~Q,Z'.˅RQwp9QW䢮2bL/JE]<u!ZK\9E]PiF W QzޮF5P#ZXԵl"D](#ZI8,JfD]Hm"Qj' ,ݢmc=6"x >y#<;7 v hs.7G}@&vEII{-Wn(--8{պ}轜? <*WEi{aPuQQ\%z Eԅ QYE]ΚtQ.\ Q yukID]ueu Q.D]ˈ.D]nH%vԕR׶<H5EmGV[x.U(ƴxb%vVu!BԵSQZ Qzm[+%q&ꢈ.PԵH.D]WKۙu!BԵQ1.D]u*;#]u!`+.ў;ul Q26KGu(jHTVʒoMZw.jljM UiR/BX\('&3(RJz_m-՘h?mwwƾ/"m QOm8r$K3j&Bԅ+; GDԅ Q.D][uWD@u2R Cԅk^9]GuE.D] <=q˹I6.D]ԑ,*uQJ]TRW&#*ue/sJ]p`0ԥQJ]^׵*u".'..E]uM/Dzw- 7"BupQ׽ &uiVzu!%*$.V:z\,@%'! eFBԵHV[5Y/CuA5+V.Q.D]` QWZ]&ʸFԅ54Fthŋ$셨k< D] ]Q7"BԕȈ=.ZkAQ"R:W"ZZkWd&}7 ϻiK^4EbU9dy5aPHmRg h5-۫L!ꚵh=.ܮQ."Y[uCԕ݈;u8˗e QD]@,Z^QD]E]QKۄ=];ulQĢ."YuB7h#"w5IA,J]T:P.絠 SjciTR m_ EMau6B@fQ(D]vWsU8]GuR.hy눺u!IԵaED]N Q.-$uo,ꥹ*LF. q]'"cF0uytMdj8m,҅ Q.D];uIu!r֤@ԵFMVGh~o5{c9]L?dd?\x3 .gP'Ou$um<پk `@ԅk/\ (VD/]o:BE 6GTu!ڗkt=D]S.5Z%JsɁ%.U$TGԅkG൰SQWqu!ڳ+kuj<*Z/zMyRm4J]TRW~lxc 6E%#BԵpZةUumPԕ GuOϘ"a؜ kΫ\; i"BԵ]QW3GZɜ-텨 QAD]`D],MFL,ԅ QmK-uDdE烘".D]Gue-/\^ w5Ԧ|x>o|#{ 8K;M(R,w#h| ZE jp/mt&F9.mg ;Scu].Gu%=Zuءk;,q1qWe.!y2Aԅk?ibiTZK4ѹQ%E%%Mnbz1o;"BԵSQWQRQtΈPs~բVrb,Uh^IdeD]Yf$D]𲢮KJ]@TZ:'RJ];[&lb>sL!KQnf圑EY*u "])wΐ^RɺHuɞUz D] Pz8uW:]Y|FD|QQW05uuFΠ!B5Y,mPT+mYyQt?WWhRQu:AF˛+1@MyD Z>`.3*=r_p J4Y5tâ e+.t.QSD۪iM^ QX d+u$T_ I=6O ꒋLuJS҅+>F'{D@+Nz'fl'%J#''KKD]; ’έ]DJASVW{{-`[Vܯ9ՄV;Oԙhi`N~gH,b7sm4N @'e"Z'Fc͓8 f+ZWQW(RkM.Hӗ5("+K( v+H@* N38^Z[5YYڗ50Tl,'ٸKt!4]7_G煨k@ɖ(u֜-wybuy.D]qkㅨ+]5umE%ROZjŢh>ED]ȕ+qsM@ԵQ7sL,ŽhE\U޻D4.+TYɻ`g%m@*uVsA!.{}Culx)~qzQ׽#HUDFuM ocϋZK\PQׄQk.Q׽pb|DF{tu޺f{rm Q^ Lu- [rID]s5A988-->)DcR-"qQW'Q #Z˛˳L jG(v JeAQĢ{i *yw iE]R)[TۡJD]+k.'12FKu-J J&1uM>'feAyTSDkmM!,y9#oѹsVʺyM,fbWWejDqZ WF7zm[ T\u=M}5J"6ڹs{15̢e޸g5 L`c5u&u%)h+Pޑ4IID]D]iمu MeEԞD]5FQuM,R9ۍC"R*_QWĨNȨnV7#!@58Hu޸.QKUr}A4q:]TJ/J] T*uKsnmԥf *cixrJ]X˃WQ'a &MXoHtF*u!2L߳uuʧbGuGАFԵJQWlQdEd(+[,mgK?XC.h2,`3kT{D]yj{ ×Qhb6i*LuM\s~/y D]\>R;u9R{R eD].o ȍ孭)/j#$*7WQWK˃ QW6@. /#Ps>gF]R5 >s@dGdg D]ޙu)vPY77:;^WǺ|{7?xgܗ?o BVqz@e Z#5&x/2= hgt~:Ipr1@ }IH9͵RWSL-.,+GueLO&9 !}HДD@MC%ۀ@Vk$Jue=Q/f򈺴uW,+~uuUu_xL?᱐,*[oV\xVzN[l Qג A$quEҜ Q$꺬kg _Mzi6<uePt3p˜ 9K':&@Upc@)~4QvuͱQwZG.rIO[w&|tvanFy.L'-6FhjÁGtMVJ]ku ~q7%ue^T/m06i8@HzD]uG8".ǻv~z:kM4uGԵ9QW8+(R/?A#Ի(J >}PהAi碮7XFNGTBԥy<+:8 0DMK& ZS,M\Db^QWv$bjсȳQyˊ#ZvEBaon|Oh=6Bq26r|&Z)ML䝡B9m$RN~!BԕK V>`OԥZ*;BD-".D]hvQWt.4$K(u @#V7E匩FӴ@uMEG=QUh 1,dc;%UYUQ͈/dyMMPˊ*h7UѦ G5E^}PkƉ 8q HBY;)&*\>sWIM8o M8D>ZU\3آ|nnw>8纹6Q쀇@L`]QG"% -iigЧLuSm_>ZRnu͐ y9]uEICԅkǢ.Q/5/؄}6U]>\ⶳ nvFˌDF\u! ptIʙlTE- 4)yKw$U]FbkPf:YV}J]ΒWk;[m7C.*uRW0+>L`8]J]j@Uv2WQW[LRר+u&% oGEz"ur}.фsQ–Q.D] ZI6$jC.D]u<|fj z>E]!QWtM< QnKĆD]'7Iu?fVl~tIf`OV;u $D]6/No&CjD]umE}$_vaPԥ08Qmr\GYD]!I-"ОP3*|,ePD2cg$iW63fuM(꺗cD]!q`SܝDqu!:KM* x{,惋E]H.D]v%H(. ]( X>e6rM( +ˌ Q.D]AQr6aQ7ru!BԵp`#y芅NW+Kt udRW2|cgC}e6_K}pԜ=#.g~zD.D][ukE]""BԅkhE]B]u ;KZV5("Pq}H. |3.D]Ž)jS0K5Q.D]uiO_U2H.D]pu=L'꒞&&E]j4;If"g Q.ËC(M'꒜MgJYQ.D]8[2^6BԅP~}ڱg+ .Z/>2|\ m/Nz~?|H 5u!TܗzR5`0&R@D4D]u!EFq숖+uZD]RtRrza1 آlr]y2u ;+X!KBNS%}Z Q.D]Q(Wupu!R>+uGcť6Hc{%J:o+t1ԅ0.5MĻG"Bԅ Qע{+9 Q,\"?#r#҄,.D]u\RR]ܠiBjru!Bԅk(.D];u]L(.6Q 3y-Om Z@"ZϤ QWzcKpvn!r%M >n&7vm^kwkJzKp~pPLgj*A"{g:27"ҋV+߷΂>uΕm9Ym( QWBk&hyu!bj@6D]6 Ku4D] ٚ ~ D]0D]E&xk]҄u(EMFqD"u`' QW&}`Seh$ Q.vB5 &k}1S(k#Bԅ QD] Q._Dԥoe=} ڹk] BԵ)Q$1ިz Q.D]uE Qׁ Uwz䀇5o 0m`u!ڎk& " QD] Q.D]Q8Q`բ;D]6'ꚦ "Bԅ Qפ;녨kJ][.T8m7ar|BuΫj\.T7ot1h˅&dkʭ.u!Bԅ QR.9ۿɒuulgh@Au=o *jY;=0}p^U3$MhY{FkB3uΚ&4i=4cz {o¤p u!p٩ ΌK'w"қApy3@Եz#p(]*Q4.I S-$-#:ezTH1 Gwf rvg$L4.#p1c z&lkd(A&uuq#:om)Mg'H\̘A ̘A FiLiHcJӎJE6HmD]\|K`(džv+v]`!lo$^$sxFLD3~.ҋl{u.K LiH:LiCaQ.)^ӥ#].JQn y~>' ŕE˅A(N (uJ}t&r-$0$R@DcRP 'Y2Ҍ`})ڎ͒ #6 O3fm;Ѻ8h)m]),{pJKDH$#Ql ^E]ҠQD]H޸sQZGquD FEDO *![*fm,(e;OsF|A$텍 A ,5/4:I{ac@ ;m,xbRf!HDU.9f  `.9f  `.9fE]/za`_bD@X(D]/7C ޚt;{ k/cr[nZHX.Li!ZtL.3bz8ђ8]:&HP0ZQׇ uIL7 ]lEKag/F{@FA7/ bbeNu{suc&]5a*XئsLiKQWt}Du AH/ s*‚N85~HHI L6`|."|w5wg0¤ޕ%a ufH%ݨ~ Zb$I,9D]KGڢJȢ]R{(Q'Uu u]ݫ1:u"_9;.@ԥuiAA"L"ᒂ0+ؽ&5Ƙ*0Ytq.j ե!}6^ %Lةx V 8DAҖ*mF~`/P0"pQ.u@0Q D]sPW-}͇ `/o??>O/<ӿ?oӏ~}:?(~TӥPTkw>h~VepԞקo_N/<Oy{Nϥ;T̥.^?TNeUtOuT=>bOQM]͹sgsuԹ]w?}t`}ׇ5>t~׽eQQǖ5(c/* O!^d;6*ӫبNc:ꎍUwlTبc:ꎍ;6*ӫبQuFe;6*ӫبNc:ꎍUwlTبc:ꎍ;6jبQwFm;6jبNc:uwlبc:;6jبQwFm;6jبNc:uwlبc:;6ӛhtFc;6ӛhNoc:鎍Mwl4hc:鎍;6ӛhtFc;6ӛhNoc:鎍Mwl4hcluzg_۟81cricket-1.0.5/doc/neta-paper/peer1.gif0100644000114300000000000002507407047764266016445 0ustar bertdwheelGIF87aTȖeGbl e/}r,TPI8ͻ`(dihlp,rmx|j@Ȥrl:ШtJZجvzxL.Dyn|N޴MzWGSEefj`rGMJụ̏tHKveoLH5ȄJ0IÀ (<#d$Gptr`ɅORtdJ,Uvd$F-yƓ:j J@ѣF"])ӧNBժXju׮` (ٱhJU-۷n:=qS]i pa† #N,x1c71cˉ1+Ô<ӞSY2ʨ]~kѲW]lߺ>];}^4 zҴC}߾=}g[};Goy3_t\hh  6F(VHfzP(W%w_ڵx2x7#v,H`*ף?ߎFΨ6x793b &HXf\Na`'LhtHL&9~g_IfC'' mYFKb2feVj饖~馜*Ɵ@'Ih~sjHꪴ*+抪}*bN&,c.,Oj$mxe`FضrmچKE~ڦ{{K+n[G  0)c攛 |e"\!lL쭩2dk 뫽hqL>lsεLJ8ꈨzlPGMRWm I(ӻh]` ]Υ-v5m-G [m&L[|,~ޘEs:-7щo}}>.s?/:Ӵvw{ b: 5#\ 퍿繛98tc>T̾锦ozP? 'r > +_1~3 [>O? `{^:A{`P vӓ;\ek'Bme 6` ,@ Ba+ {Aut "#6n#4a (Iv3]v. F^x5L lb±%w$bF)nThE,pgjB( pi XD(N1D[ hG%"qde Td"WY=DB,*H|O~䟸'\ޯ0Ha~K].^l3YjR\  "ZFba"+i2W3B,P2QM%hIMfn;IPVJ#-U3'BNEt1$Y(ӈ%"(-ILo+ё⇔)YЎ-儩"Q,ѝ է?M*r\wϋ i*#M+')ӪVJV%PCGcSAj)FgEk( ORLU:*vY+lCU+ n9̓ism1Lk:=3XiVְV41Lz61 MZvAUk TAi:٪ӟ*Ճng=DtDc*oۤIni Zsk]] ֯](m/vfS[j\ⴽBds{ JE%r?UzNhMp ` %v3:]Zztmop Ed§I:(Wpxq!+Y9kr~uVᲺ fY7Wc9ur~ޥWRY2x{ :s)r"\?P]?j,2E"\#D3ZahDWҐ 7NSzҘt-FԥN5WVծU jNkԵt?]Է.IhY>jH t B'Ar!c;cRsj< k oT6L'7OF~nj(s#&j]"93еmmAv L=qa7WG)޷<^ozܤ9 WgxvNʘvEf9bu P )R"81yahx|pȘ=ٌImFy)7™ׄJ7hAWhRƎxa噶SF(tՙWy*ǜJ oy١9Mɝ}֑5˒w{HPʞD$D⸟Ix9 i("ZeQ\yI99ʏa9${Au3yW6yӸ(*u.) X2 i^< !XI*iZd%qLu\%$'xe׉I&+]:'Ud?J_X7!:#ɪ\77Hbgviuj`Z{)CIa2z*p[CJ1 ЊϪizة9NYGbiI~'vԗYM:YYk~SCY~zY}q>Y8)Z,J%7,OXZU]Ԛڱ걲)j>Y*UrHH3١ '(R:e*rՋ6+:C[Vŗ1z[3ZS[nNt ǵH`[ &n֚HBIrJR.# W$If  ^\ )<+BH9l@ Q4z./ ;[^c ݪ%8^ѶK0[U ^8)Y$;;n > RH ۴iz81r]IE鹾ۣ6bg!BQ(eKf鷪 {4RAPpo渪T;n|kwgƵɹV㺮gL'?y/گ:fy|W*̕Ĕmy1=ĸɤ˿ۿ\/W;#{Gʴyv$ڽJh%'syK[[i65 *\4 al%%;@ {ؤI,_M̺!hˌ+_k(Y<{کs`<ƂJ8d9 \?|sNk3iQ 7kq| J9f:ł]V|XdvkcpXb%۬KglɛmKGpd* ' J fw۽4%9KGo ؐ|ÛlfӼpRK|Cr,8ɻk\eK@lkvkΦkhlll&jl$=ѸђfҪє6]4ъ jѦ:dÇmM\ 2B5$r?]ЫYC=+I׌|]VˊO] %u:ܓLKY`v<1[p׀؂=؄]؆-Շ]H؎ْؐ=ٔ]ٖ}٘ Kgؕmpm}*vׁmօ]ڹصq}kٵ o=M؅\ۆ}G[N_ԍ -ړս۽½ݟ] ސM]ڂ-Jz2mؠLغ޼ܷ -M}ۢ(΁ eoTشܹN=ٓݑ-^&~(.]#{ʝ⃭npm]=BF>-۩O֞MSW]}j2!ؒMip-Q[0Mߧ- G‚^Yg)j.}oL(\$|>dW/|*+災^M2.>;' MU^ANľ nʞ^.ݦceTHn.B \kⲎvnn޳mZI^޺_N/` o /.MJX.Z>/W[ECVؾ>T?_V.?ӕ/闞*?R*؟ڿ?_O,:ƙD?R~N^i; KR_EV"yxAuT@T:F*5D[pX<&sggtZH2o>w|^?/#99 ?<7&•JKK!Oϓ "RSVWVTYYT[&A9`_]acg%GGLI-0˯LM;prtu=?syv#{k D^b%! ,rR-S1`=bIIEDɩSOa AGI.eJݺxkzi}8]kPRx+C3RIfڲ#4ښ3ۖ.p}ղr[X?xrU'ڎfСE{n*RB8*&+~'|6r7o{=w&P:zݩ랏=G?O8j2 PPAb5h !B g(-83*4Pi-qSK0Nѯrd`kJGu1ƹR U1 :d\L1*&1Q0[q@(VL4/;S BɄmέ KNPnr305S8H5MV[UjrڴU(| <QI=*C0%5HPF'5XD3Ya}Ht't6C"UT.ZWJfLLWwXc!wWNsOi`,bw`$$-#lgxcGq~c;Bvۭ՗y: 5[*f~d2RV6C匆iqjZ=s; L.lοޏZܸܡQ]w5Bf GfDp\C, d)l)s$nnaeXϕd Sy&A\yճ1ٛ%L-_ 7xC7tß%ޅs&ohCb^OV|7чNm/wfA/ * x@Q^lmi\ Jg{{c$e샤Uw>t,gBf7oN 6zbQHw9$*Ǡڨ wb F`X|TB-8VgFh$>_;m Pܝ@02)qD^pA5,t!'ˏ pK"ऒ8IO9$&KILҔ$(U9Jҕ%-m[e/y[e*iILR"T'wia2*9M_f6yX^S,{=< NxÐy@&3QT0Ό&TK&3 e rOq;;_)O+Ɲ$iI0wpD zł -C':+_ZxP4 \hNGG@`SoP)#5iU,p,`> UAmXbӁ:#DPYAn#,Hm B.몃%PzzƋLLUld)fѬimX[,~6Z+hIU4zZBݸ.{[RT-lak !Z,_f"7,n0\F׹dF5-ml.ڲ]Utbkpnl|Wͯ67 os{ꁷ'XP[ᶪm 2"FpQ7KW͖/L*q,FihgW70ukY S f0d(G~;Rm)mbh\[|b2-`8a 40bq d{``)?Ln2hHGZs*S ddae"Z `s\xHie_, !okk:psnu _?d dGε/|ڥChI+z܎Sg[& i29nֶE4 @j1~U17+4^xg O͈gԺ.!cMCX5q,~]]n)3`49=^s\0*a?p n^mar-rK]O:tu"'%{z6߱`gv]qb޽NwsMzkޣY;j [ z _>l2-zԧ^ 9d{ LbUc]{8S S,# Ad _#1t3TKA 9;8 B =$lB*B3@LH= 9+D4.̭12\ lBA @{DA!TtD`=|\I|CI̭dLϴJz˱Ln#D{[DɚM @9dEػ͵DX$L_͖CC,1GFfLJЄLN\B@<;J$ClͶ$N{MlOlĹ|JTN4NJMLMP44 Ͳ\LԵGO QuϚP@DDP*P}HQ;̻HQѓQыȍHR"ѵѬQ ɕQ(ҷ{R|{IDJ*`R/@9 L.O20CL = mP78PtPЪL9]|LS)p$/]TF]0UŢdT3]Q6=S@UTNSM==>?65AMDFUZGuH]I2C5O SRC@-QdTeLdU?Z\Ue%VPPup򨤔Z WqM[i3M^RQӣk Py-SUahTW7=VP aWifyEPlEKm}(@%V/WDgVhWj׌=XDeDUQX\X4 vٙ_},mrQFMYٟ-ڠ-ZZ5ڦeڥ٣ک}ZZZZZڮڡۢUZ%[=[E[ZڵZj[ۺ[p۽ ۲$Y}˛+(Xꉂ\1rƅƝ0x0˥\ \5-p]1\U*h}]ٝ}Xق~+^-^3^{C\#/R^^?=^ __ED-_M_]_m_2(>Vt^^_R~"%`6`R_Jx`x_?Xx<u#`I $ `6.a:a~^Yan '# `Hĝ"!"_#}za#z)vbsQbf,$((-->a6>c6. 8ca'8bF~6cAc˘ccA>'<a@4Vc?>36䯰dKJt(N tJNenJF(U壔dߡa.Fa)bWe~"eYa-n#f^.f_c/yf`baObi-\\ bsYf7>>p.`tqv>xwzt``nfhnd>hNh:^h~hhghhhhvhiߐ.iNiޓ^i~i&iihiiij=>꣎h^nj <:L9jj zjYt]hVnjj^fY^k-f!~ks ^bI2d澮efgF\V`6e*߻lZN$cbvFk)HlBlumSn^>^>f;^Sk`>u0Pml]lV`VyFknbn*Vf_fO*Pnn~cv16mo>lޡ߮.lo_Un>l'6ncFjn f q\C6l6n_5qqRqqQqq+qqq r"o!/r$OrHh%or'n&r)rr*r,?.r/r0s1s2/s3?s4Os5_s6os7s8s9s:s;ss?s@tAtB/tC?tDO4;cricket-1.0.5/doc/neta-paper/peer2.gif0100644000114300000000000002637007047764266016446 0ustar bertdwheelGIF87aTȖ-Gbe el}r,TPI8ͻ`(dihlp,rmx|j@Ȥrl:ШtJZجvzxL.Dyn|N޴MzWGSEefj`pGLMڕ̣rIwlIM\2P

)TViN\b%#'L@ bFYb0&d (V'<)gw}x${ɛUȇ]eF*餒jI饘f0&2gpI稨|~h?+~䫸x6 +%lklk,&l>+mNkm^mnm~+螫.`"N jjoZΚ= &ti餏fjglw|嗬+q 3\2ߪʲ)p1첾8Rع]t_tF?ֲͧk_&HA h٥GB&ˏ Gz Hc52Xc!%9JB+d"7 Ie{D.)lSi/TD gi3^qY,HF\06+[/#w/J!Q-$*I"r lM^қŤ6y_\[k " p#5iR|7O]ӗN f7ӹł/j4],#OJ==3%k.u_zP,T #9o8ΑvA&DC}8Ӝ> Ԣ U;ҕ?OJL&T+T KDHѲznfG' xөkUJW3k]Wկtk\ WJU lb؁6vvcK e2xBECKҚMjWֺE+lgK6 :YNֲo%V%_{\na"7]uW*a{omkђMz;V|_;a?굩nw[k5\cW{m'\Hc|Wr8CX3ֺn0ID38J~޻f杯wv7q\+"/^ޥ4dlm~(0]_\7,5?'b2L _>ltv#XF0G 4Z.SfHp~HgID-wH ,H(,H 0o9y[}N7[7~.(_Yws'}3}%~g~ȷ5zuY|xoza{pvp{7|ׄpvghm6wLgXFڥwQǗx~Ncy*sǃvqV8z}{}Ew>d$8lDžgzzR$gV腘wXhrX|NXW8hm@hl&}~GE(X[-8[})hu}|ZB H}lLs8g^fzYܘzbH|!eXxe?׊kdS%qp{uxdLjW9qH|6dӈ8It,xxՍljiTvЈ%z'W|{Q,g׀m6~ֳSؓBDFYHyFIDI؁>)TO,S-I2O" {+ym9KHڅ3ؕnvǖ\y`h"`Y\-يL(eYo^ ZvizՒ٧cߘv }z!ɑ7h\vtYnǧ+J_Z iyzȘY~YEflhuwC皫hfppo)|YYi幚`IxBOZ)I՘eY~yoqq֩fY9k&ِٝѹui~‡:Ȟ=GGg_ZYu}Y5h[ Yz8ʉ 5JIByHIHK N꓀JɀIiE:w8ɤ>UPi,HWY落0*rW6Zp8Z >z*znT z *g rj{ z/wϘbzbi[ih/jsZ~vWĨ:zo-jZ9r:JyucZZzZ7ԩ*9ꃢZW ՛ۧEeɧ#ǨYsʝڌ:jjxڰg8 J*@ɗb)E p(pŸ|'d +˯m~uuF{{͊9w8 XPQбt Z";=Fp&;)z*<8~Ee0Xj6˥< :EɷRZ{Iɸ7,hHd*Á}rIN85ZcK;f5;ڶqEvn[XvxKF=ۯ; fvDY {#Qk;'Y[ƵT>;_gZ,;.{u[Ł8Û:m *@+!r+)׽)2hvZ{ž&Lkv;+Y7 Æ׿k >@|nA\a Ƚ nHo֜5v˷jŒh<̼FivɼL\<\||ּڜjVkΌkk\Υ΢kSlY hU?6Taɬ b9Ɵ,ЩLC<3Ħjw{  q|?<mЯijbEçqmݏ-3-َԍ̐Fܰ-lѿMr ğqڴ-P ە]!M]_}?8#ە˹f-^Սy]ⰺMm]“W5~ۤiӒD.~GށˁVjJ.N㐫=(]*һ+jn~~m&z\Kx^Hbv ^ਞ[ iˋ^n =>|y^^-Y޵)|݅~ԞHl@ ܐnf~޾Ɩ ʦ֞.ĆuNh}& vLGmNt\Q>]y.%'dn߇ /-qNޠ^j=qM|>}fԄ/N7~2 zZ?pzoaOEt'p*jm _Mڈ8/ n탳̻ i{PaL(e;Mg-}Hs~klS>@>G:XZ_T^AH>z߁Tz} eڹ_.\-V* `ޗ+[\Io}m^n)>_cf}(RR3NG GRm.e=9ZN /#RA F!ID&3RD9塌(b:&S*i <Ļ>DCFGȀ J.MNOPO !JVU#W)YABfhiH--/p0rpj:W%ɨbuUwxc#(m3lj#C9jI!2h M1Z-TI2Ju2ռV\t[*rq O0~ IEk4zdڔQ!Xs޴fEp+לʬPFvYL*hU]; ̷ua`㰯% &9`ƒIL,6:ܴHܙ9L^&Bҩ'YZNf*;%^j~]u4/ 1Q^:3uº붻^` lXFlxD"?1P $P%1pLp +B 3pC ;dFDS$0YDEcqF&jdQASp{$ G"}4A Q?*! o5o=/{O91+.?mAزuR |̟\ .΍<^/4!RF @*S*N(6NSM5PWs-TR_S;C#a'N.% My8PBSرxb86cSSCb.V[ >[gn3f fayءs9t4ˏ>9=:pkvdCWܣPGx.s>yIe=۳Ι,iIJL@̑@İ /r#,I ?!pA ڷ>>A Q ? VHI5`A"rHHQz ۔DO6 wCT3+1O@ C~$sa9krbYQgܚ"Fm+[;Kcԏu}Ij]BQ? tkE8Ȼ!R5 k*"ަ4pcި*S=8 X@9duΔȚцÌM1$2+,e%FQh7.DdʶԓAysKz;-xSP#ɜB(gPI ~RTtb+ -i}B_VEjU{Zv"}jIJ(mo[6mqr"ZlBWU-s+Z7unwi5mx{65l׸=D|k_חxnX}wK[PPtJ$XѪn-a7!$t6µXʝX4LmldXdT( 7u(SĪx_cc-xj-1#;u2;/172~\X~k{y@>p񰕚۾t*=Qk%TkA󼞋lY.[(?:J:s_C2 z>ѹ)Q|nL@2,CUX $)@8iĺ-d@+}AA@y B2D|1ZDFL%{X9=@ن;vX*o+hJ>pB!3CY30 ^8:MԘ q"4=TTdS R)џ F ƽlFbjFIG Zqg;˻ ak ; 3!Ñ:;nq,GA]ctAbB"m:HZ\H ,ר6E#" ֛6#b0DjIOS82[TLD 8szbZ?ѐ@N yst'L3IH?'$FƆJ{*4 6ʝ+9T I$0q7IkD4KkG˶ 'ٰT! RK‚ ,Jȴ@ dJJ|Lg@LLóTQd)dI =LTMÇd>T> %<;ptD{B,Tlk=ҍ T(C D(zŏiT]m"Yd0Z]OQiM2JUiDG]6< INZթ^*b}WRU`|lsMQѣHb*I{ MZȞ]LMԹW)ym ò5=SIYP10*wQVeEڽPX4Z ]ڬշU?$YVEʶ' 3ˍE֧UVb!+"s/5XR-U \S%%e \]==Q!ڱ;]-^9[}\b}!7SL^Ż#˦CU\\Z0D}؃Uu3d&4^- }_|]9 cZ/F"0.b.0` ` ` 6/- 6/>/N```bjaaV`*-4+`>HvB-d|׍MJS*-_m@ͰRWe4,_j>K\l]چ_JK)^S)!6b9>bCCL.[ۨ%=-b0b]Ԕ AgsNMgegwF;MLdCbZ.VNjSvf1BNgQ ?e`,wJ@[Zzvdf|.䍾hhSHiyFNeQ@f6g2bK3Ci>PiwcTnZiUFdki}礮S6*V~)~B0GknnFk^kG8kdd.6kk6fk?"lj&荢&Ȗ"lclHHdž[.m>mԦ~m؎mٞm`aAھmmcmv7ScJ3V5}!nf^1)+JynVn.)c7nj&`,b!n,Sxoߥforyboo01 p^}0޵pGp o3 wG  /_p?h^p,wqqCxo~"#rhrp(?)5,Oж{1o*'o 3G5&20,98o4Os<~j$_:t$f0 p(&p'^t7pG77_rGs CFst3oJwt4NhnK"?u4XYo3WUuY+[uX_p_?vmvqwr/wsOwu_wv_nwxwyvtw{w|zw~w'}wx?/xOx_xxox0xqxxy!8Gu8z2Vyn't-$ypy(Ttos3AyO(-=Wvfy(yco_\udo;Vz&׀]S%]VW;~zP'O|K{ {7 ')nO'zyp|?s>'U_gcѷW|?ƷGqsEGG|)h2/J?O}W՗'ϟugwv707{~zgg_{ a*xxJGA'rrj/z?#Yf &ۺ/3]7;O'#2\2oU3*R+6 zj/8,_3:^6<.묷=] J a!b"c#d$e%f&g'h(i)j*kc;cricket-1.0.5/doc/neta-paper/ss.gif0100644000114300000000000005354307047764266016060 0ustar bertdwheelGIF87a=U3f3333@@3@3@33@3f@f3@ff@f@̀333ff3fff̙̀̿33ff33̙̿̿3f,=@pH,Ȥrl:ШtJZجvzxL.zn|N~y++u''G ~ĶZ+ңߢ莠| j;լ0>)ΌR+a1Z'a(jD!6J~>ʤ,#)̲͜S29WjIyf*-iiVFJrhGX5$CNZ1lɞRS+u:wlڷdŻdY`͚n!׏y֪˿)ޥۖ1ƑpCydXhWO6ױaZTo,usq,o~][j^w+ݛ ǿ/7g˛G߾;t#Rŧp𭗞ށ"1Yy^D |V~)oMhYY&"dUj܃C醚n.H"(Fǡ6 _1#Xo){b9hw)'`]x~?:G j7LESnɥM"&W7Om2砓(='abGC@ i~CPe#)NiI)wb^Ẅ́*zB7+ Nz˜P #d[$ŽRNgjxz ecKVgJXW-,$vhȲ˜O`04 .\EL4f ,>[q(o#1$ĺ#OR@Ю>!bRd0 rlDu0lOL,4LKL@R@ /,^!t`<īp(xȮ٣ VC <˲a7°=.;.!;˯҂ޔWƘOpny:#hL 0 嬷NƺCŴ򮐮 ^dǢdMKȮo4( "L)L K?Yy8]s/K=;E~7/"ʫcFP:OʫڅVj~ IBL"F:򑐌$'IJZ̤&7Nz (GIRL*WV򕰌,gIZ̥.w^ 0)Q!2A2e:|4IiZؼ6mz8IqDft|'7*cZΞ>Y(|RΟD@͠"!B͡6aDSsEè\cz4/M\ uGz5ZEJ2^D1NPzTnWŪNUv5BsP홵(ԢyԵ +_oY*hPc QQǢABXeo(|zA8>1fRIS햪4v{lJ[X<ncZhhakuSr_Z6C-spr]oW+cv Xݎω)xc27gn[[#~ Vwm:[޼Լ2&X~}C` [``j:a {*pV8ɋ1W}헮 Y0mkPR+-sAEu j|gCqoe2oZ๊e$UB7?gyu\uy:.*LdWnrL^9 Lf9͐)-96G/:5g@Ԇ&5%ؘwֲL[P/ri\FO)3ph=M=bn(#tl~H{׮B->hCa۞BAmnD(pmiquםqNo}[>xY';.M yx7qxƃ%n#8ȳa}&8W~U<啦6@ |Q8ҋWo J/A)zӛ.I;!~I[tRe =c9.<,ބ<2: .lM/erIJ3lo>߂EWKtY|//wgSCz4lz 2kh^ĠT+oj tef~wO#<3~^֦WS$A_ԏWUWVc4l!'pv~Glp`d(BQH ؀u8%'Sv")(X1!%&X=-uh0D0؃@hJ'u,ȏ7I@I8֩ٝ}fxmV \^ɛI9뉘ˇI6dijЉԹؙȝ|i zȋG^9)  YHW9fE.WiȜfyobWu 8Xxʠ(^"9Iy )ؑHh#i*bll锆y~v W'z,Zrd w4ruxPIvls}q@!b؀VlJs3t:p-nzeJyQWpn}1Z\k,J}x m:zciadX0Ӗ}sj(*.,4h&heYU>"aڭ!TYӚwiڮ e Fe}y t׫|{`j_'uk[gEJɴf&ijcr$~}zCFw)z[{7 u'GHYhF~3k Xl`js{'k"{|6$@;|%'~7rGjjug"S{jPB{gyƲ}{.Fʱ!˪[˳D.KzyWiV˷s:Aˮvlr&Dx&e_ Rl[eYm;cZe{k2=P˫ `Ȫ%ӼvԛdĽ[$R滾@Ji+KJ`{KI ۿUY| j$<\| "$NddRݝ|7;=ĮNn~n`>ў̾ۮ.n~~Nھ>N~}.mU 뻹K@TP!{ydSJݑVN' Rݚ-F`~I/}p ͐xBoHK>@3)_Qt/5q'Td):/^?Ez~MH:thV5S *T:b~kj3_ߥNiRmzXȲ ˨\a@P0pa |/V.-Oe֗oߖ>ie6n\ xQ`fchhp\*oH@bL0LOʔF B[o]r,ήe nSY9~ O0PqϐM@N4T`eœHuT4uUHUvvw@WWhWwxw`nXy::Xx9Y`V{V:6S<SrX=s]h.Ea:׏!N.xQ!AOEgL#K- Yn+9};%-ΙcJ%<OgɑcՒu ې&ҕ܌u͋Ђ()`BjHar3mnfjTXrkEs+6r(7mZK5aj^]nͧa.d5o]~:Ja})mud^t֎m ]9(54sZIsZ'G)ߓ!o1C m/8 ^ iK΀{.rk0 ˋNlqŐQ9q4gիLgxn*=>kI̦Npy[d2m6dL<4ԤxDN5$L|x"N:67cO8TB9m3> ML6S3)L4M,MQ=L2V K[^UP)dFI IR^{$\y#V4\Yr15#&< ېmxŀ`5rE7UJH%QEQ!VQՖVdk=M_E7>]_EV[Op:dx3ec!ZaA"o~R'+;:֝MGXk^'A:}$5S.WEF\`_61-bXF: y(.Yo%W#YHfWh".Γx8tO 8;N=pʱ:Ɇû=iPDړ߷S"O N.&fkqIr};)Lu|w7kO`׸ -ۘzΛ_$;$}s{,vp 4P8ٯnʳw>ȅ 45"'Z 6WaK[H>"}Ĝ R3oPQ.IJ3$: 8)U*SRxG m\&XG6qxzBԘ"E(IARS%)35SfT$!A5;-OhtT!'%15ciI3r$L h&3Ƌ2bKVPVI8a ̜||ɤ, {o~.EYs@Pwqc+4e:ڃ Lś RWZ#Myz잟 +]^ᯜJN|((lv<3C:Za9> {4'=/|- Jvh0Mjr;;5~_XZÖ :tǚla֜8{^E@aXl.wm\iWi^w/SU=J)3gKpE8$3GxsP=^PS])44Q60Z{N>|"/9E˗Ѵ~䢵 qSZݓzׄ.`?l:78y z滞 [z!̚bV:ҟUwU{ y{o O{_|>O- -lw|~ȧ6!}S?!*x/B>?7-d-9Dw,`Ʃjj@NŞdnrfԮ/.r.4}pVGPYplCRk+7l`X׎%fJp e , Otx0Zpn rMnp*pÀ iP#@F$gEh C ܲ Ű F+M SBm %iĚqfqp=1jK.^-/,pj01T% AQ;V#0R/1a_jwx}1!sQEqЮZbVe˴L _ǖ eM(Ǟ m (@r"R6&Ȁʌ P!a,ȌZp=@Qֱ&劢$nD"I]єPmTh8N0Ѱq M YH  SJz2l-RF-!'& Q(S&&ͣGrdq0  ꤆Ѣ22[M+Hvn=q6Kܲ.s2.M3: u 0-h-+ 7 1R11 87m3C3I":3>QE6Y55R53]$#6(<;KC&)'r7>1+-8?h23 7s!s>s0CsCD<;2=#/p6 A{ ml%iL# # 2!JD#H"WEYtDEkE3#Č$)$+2EUE%[=E#Vp;B>S3JC1/{qCM(UT>??08*2Ӎ@R 9D7r>;AJLBᮔ54CN8>Qmn?09RKM@9 :Am66QP4PW5(5Q8voz;t^p5T-3lT|\ KpWtߗTXzzQ~+_li7ir]WRY9cICC` 0*Aςo18B;X4 M&x r{X Jӯ *!"<X2(`=zA+qcVQxUY_|h X664X4xS⍯ZTb|,}sL#Y'Y3Y7’ JetА=Ԕ_B&NHVYi"H y&JFSy%$kygmY](>BjYNA6 yn'C=ƚcY톄2AY9!_!j"+#CuowYY:YZo3Νh#9<:I٤K򂙙M/7WEZ=:S:uz(.:2jN-!eɁ$?zA_ ZzYC 9鴚6*qӭyb:Z횦{ڢz*:w織gٗmpj"'[β';C:59!{;ٵ_c*[os۔|:Zux}[;?;&[i(l[ۍWۻq;Ǜ{(ۼכ)ӹ[;{4㛾[ʾTy =[rc#| |IqZazN9sm[ñZ׎;;aspVG\E1q|#wДw<[<_os^w{^话^闞^꧞^뷞^Ǟ^מ^C[ޙ%6%8<}Y ʝ'-/z3A|I_/?gS=?'ϱ%{cߪSsȁ#Wwߴ^Y\q%['}kzuGq{!h %|B)jb-1l>5~9n= "&*.26:>BFJNRVZ^bfjnrvz~w #'+/37;?CGKOSW[_cgkosw{{@;{?ʏ:gp!G AHaÊ'ŋ?7EAL*_<0 D.%3Vƈ#'\s'QU"Dt0[D6񋴭LG$,eFÅb,y2ʖ/cάy3Ξ?-zteON}mb Atb* &Y{"{{E!h\O ^E;S=^x}m}b ^ty_Kg$vWuN^]FQo6Q L 4|(}&"E؟" k덈b$.8ރyW{|܄H1aO?A9E"yI2MBQR9Ub qWr]%to!4!e bjbnbp&'gt.W܌0H[kf>ai慬dse޸C(*_u& )zn^v`Ed:*hҨ.D(lє&!yډ >1ğ2 hRi.{mV7hyJgd, _8;Fd HǤ.u6O|#| 3 C3<O1s!;l')<^/KI;D@54|8+N߹ij<\-ך5oN񳷜r b}Wwc{hJoS۳^R^g_ n*ғ,9-ЁıRYiԍ ?>xPf$8X>_>tS}&ȉ>)9>I8)Nz&2z':N׼adg'ۥHR!Se0A0И 2cFF-"3m$? |fmwKgԡ01m/29ؿCk͔OYY'aRc ?rm N&Cd.DiS{ Fz925Kj򲐖cI2!לt]+SRAr;**[$PJLBeW9!NVKeWuɝE-WsFҲQ(&iSU}E]bYKtE8G\s]/5 UυH մZ&JWRA ;7ΰs/Y~[e,O;vC hwi%Bt[Iw5)^qa,c,Em8!w;c"piE%'"&53OWr]sƾ61NMzQ^mhz֔S7(gXjl61k:v\"@kG=Fwu`[-ƮAonJLgȷ 4gbG&.g~so7mȅVa/~9V)pѠ h_}t]̯5Lj/ޚ7K~zk^c0&vXRݶyV!}{^nr哏c>(kʵOzة3+6UI Yٟ5PXL^M pWSxr{qWhx{UI{Wt` ~ zO8  W~W 9גQluفqQA_O5e<έXt]!!b|a= iPy}R`2A_՜>^|F)"r$DJ"$~ хƋ] ~PXH!JqZ:.՝aYt$ޟ9"!롡%bjqz"(5*i#~R᳄"'aqbvPLF'Vb3#c$O_=}̕`26cq4J3j!S(`]G+aNBF:0cb#aC$c@k@3$F $6*IBaơNO[ J%bNN$'V'av  $0#mSnW~%ŤS@&$1dV.WP1Uu#w   I`Wbfbf f@fa"T_Bf`Jffz8`:ݠf=a@in}O*,fW͕EPNWͣ!6&$>z!T%-JFJ[rbqX9's=!YY:`x*P[2F5]bm&'rd6$ם!JJ!TH{rRF'11g$W:WKI%X!Zᑧ<]7G)nZ%&eVR's b ocp#U$h|6sNuVvu~qT(Mrnr"=>[a'մUdPpVt~Tg'5FrB\2v#%^䀲#`Ѩ6ߔ%w&f}%y M^"negZe&M3giQN)VWNgU-KF)gJiB皞FmA*i)ϘzzJINFޣod0$Kҧsg (K}р&ʤ"av[D gr|e ifd~+c~&c+ifk+kn|`e뿎_ O*)ʎ0)*?cbbg)f!鋒mUlV-~)>!fZQjt6&T>vUߟ^,Ĩ.ble@bҢh^:F*E,tgߪj܇(^a1:#+Zңn.Ǟln٭gblKX(j&xLnMjҶ(2N*r@L}!螡g"$n`cM ܵ>Ӑ>Eﮒ|mȞln.na>2nRdÆ!ӭz])2#іJ/&纮hoo0X".z-VR^dmᆻ!LyZpbkps[Y0_pȌ ʠ cİ s 0 kR p[pð0U_ LWͦٶJBnKwR1n-¯r1z˯BC5*fնopgY2]싫Z*ǥ㧮.."/>r!o7zP%$K"b+fPf>,RfqCX3r+1oò/2O23%-w./wy~\&E$2#76/6Wr)Oo^24S6W 'ۄ6<3I/g.1 Ʋ2/3[b3Rrb*{6r80;?C]91qXQ775CFSs5ks62&wFoBtnZD4 <7g&'#JE4$4A1->B4+5Bu>4sC[[-!߁VWofW&uf+Y5Zu[Y蚫[ӵeµ>Z,i.,\5E;3$sNr?P?.s $cob/5b5O35e#4d+T{*s,,I[vEjt"k@4gvT69tuvrJ5=ga3@_aqwYJK6J J$?wd/tKv͞2 dlgaosMCsky7vz6{o'Cv-!|co69iu6?7~#utf72?W-{n7(3{pCSx[^x!|3|6"]׵W󓈛xxo\x8]_U;El\xkyW8""ps#8wQ&}8s38߷_tO9tou,!3h{8o+Boyz9{Ou|7OW9﹗_9g>AG9_77v9wk vSzC8[zly|ry~wc69˶/7/X"Rh+`AXu {5{TL,A:9JxS;rKyztb:׺w2iOx;4o7/ߴF9q{w;+ú{y3%1sk3<Ǜv{nخH?x |"<ÿ=˼:-PO$eK=VCqTT>UľU~}Џ}3ρ>>#?>6~;G,5J:!2ɐ Ŏ[AcѨ9@'V#AQzM!?@8$Ga`L&LdT:VUjWnX<@2Jfke[n}>M\?@$&B*,E/G)8ȹ4;̼J͢NKKQ2?MGGGXHILY.;PJ\^#Q_Za*UPdWf==¾R*jֺkOaPm[Ao,wyz{}~.d!#g >XŊNG4I)XRK1ea)L9uRNA PI+RQ4(̑mYf6(Mq Zwqeժ29w [u`h~ 07%aoLD>6Eڟ%B늵ti>iX MU;1#tt׎Tue}fل[hǃZkScmxr`odW;ę"u3=}w:o<$B,- Z0 4'ImB КK<|C9NM HۍLQX\E`,ъLDQ,\dst(%yjJ0}ɲ-4̀ƤL4t5aM:|&NysO>G=tPKPDuUQH@#QL;ԈIY RK5TTSUuUV[uUXcuVZkV\suW^{W`vXb5XdUvYfuOvZjZlv[n[pw\r5\tUw]vu]xw^z^|w_~_R2``bn NxY`(c_@R~dK Ydq`|scqy瘃~g9fgGhikڴ}P8jjF:ks6vvZ>Bm.njyd#u{iGo#|r+r3|s;sC}tK7tSW}u[wuc}vk0dlwqZwݡǼ7N 晷iFn>#o0:|"O?|}(}w3ӻ x!(ԇL7IC&}Pw+a MxB=@RaP&E'3'֗M!zÎM!w2C- B&7/c^Z(ġ?EbQ<,W=p^JlTshGz CHD#xG:"k% N瓠!K.r1A,Ɓ9ђuҖGGZ*W-_tf1yLd&Sdf3LhFSӤf5yMlfSf7/}s]Ó!NKI9!:ڨf(Eɝ{瀴n~4#$I~OB勠AЃ}#Y?2thrn&!Q>%P_L)WI^4(b[A3e,x?A zTJ4JC SFR _ IG>!n=:,@mJȚsM$jz82|]-K-҈yvŝT~=g+U܊,!\60kSlhEa.~W%oy{^Weo{^Wo}{_Wo_X&p |`#jJp(k& w'ȰC ".P@]bX0fX|:q}c YC&P9Q1" d( O9c,gY[klc06u "/"'L7R,.YdwL?h&3"X@HF8;!Us ]|P',ZifFYmW'zǹsxюj6 g)SQ~s !0ۄ.pc,g0(3y:-p#W ݸw IVCN6[l#V>x+އѶzu`X7G\+ J rB wt[|fV sZ>2z|\Fwkv .BphwBͮI6U1->FlXP-#@|[l'>T/w_΀wۘ9gqw.@+tq,NBy2dR󦕝pW]u{>S^{yw9va^O 1o|9vuk$xAW3lh빜=iYD*gf YZ(ں#>=c11oģ$#17{5^?ihP:.P03dՓs[¥1)ҫ*&B#?۱s1SA9c0H<7xs۷[\7[>>{K@T"p p'BM;BC:$DL  b)/ ;0 )&'޺ +R  7;Y˾㳚+#FǣC{3{9 >zc]AdL˛XD<.(GJ\L\KvғwL*1-#xzSکBQ.&\=l R3CC4Cۻ;3yʛ@y{>>cC4k9ɔREkT쫻VlHWTUTh Pxm-{9.zE{ź)ġuG~>뱄W^ T<I-FUc%I!Yg=VgVe% 2*%Y PdN/Pr|r4̬Y1WNcG.P-Z5ڏWcezK\V[ XRfuT~e9X;Xf H%E<8# pT/u4rd٢",@*Z};"DZkhި{?ғ?g^_E<ؑd%\ DM+֑ g4Ds]8$7A4D}VugT┆8N EVoPpW-WLVZ끨3Nmav$fuaexl_RUK6M,A]E:7lX ]%η8H5 [-,\?lԷ +uL ^r0P ĝ$3MQT@لdKAaC&nDnh/D߅]H #-]uI&V!_\5%l`.V[ t-u0`c8^7: #daiBCJC=fGoD/-:XIZr\9tRR&[>Q^RvNk] %9emPD@ ]cF fPLr0㐱,a>&AfHfkE6pnGfq85Ԅ˽ K[wn6ﳷe ]ggWô-55kQnԄVByK[][^ր }H55ZB#BaJ\F d[fCdfk~3fQC\>C89bEF1iRz]KU}iJĆM[};QNVhvoԀE<3 NYN@8aL"E%mŹ#Ɖ\Dne==vix)_(1<>֟k]UXr[xXe$øtC'KKj' >eVV;:wҢ˸nR|>JFFsL eN L|mm X n92;Tf1NK{ߌ#7< N~kH~u!/09X/s4TL 4ܞHG8/ 7,'HR)ٜ$h2[s#յjm Yy@Iwr#ذu2+ 1`{w|w}w~wwx!Dž. Д!@(8OxBA?*(8XU GH_yoyyy ;cricket-1.0.5/doc/beginner.html0100644000114300000000000005264110031605175015341 0ustar bertdwheel Installing Cricket for the Complete Beginner

Installing Cricket for the Complete Beginner

This file in intended to help a total beginner get his/her first installation of Cricket up and running.

If you follow the steps below carefully, you will have a a minimal installation, following a standard layout. From here, you can explore on your own. When you need help, it will be easy for others to help you, since they will be familiar with the beginner setup.

  1. Insure you have the right version of Perl. You should be using Perl 5.004 or higher. You can check by running "perl -V".
  2. Insure you are on a supported platform. For your purposes as a beginner, any Unix should work. Cricket also runs on Windows NT 4.0 and Windows 2000. This document assumes a Unix platform. Unfortunately, there is no comparable document for beginners on the Windows platform. The instructions in Installing Cricket on Win2K to Monitor WMI Counters will help with installation, then refer to this document to learn about the config tree. Do your best to translate Unix commands in this document to their Windows counterparts (i.e. Windows xcopy can replace Unix cp -r).
  3. Install the modules you need. You need to install the following Perl modules for Cricket to work correctly.

    Module             From
    --------------------------------------------------------------------
    MD5                CPAN: by-authors/id/GAAS/Digest-MD5-*.tar.gz
    LWP                CPAN: by-authors/id/GAAS/libwww-perl-*.tar.gz
    DB_File            CPAN: by-authors/id/PMQS/DB_File-*.tar.gz
    Date::Parse        CPAN: by-authors/id/GBARR/TimeDate-*.tar.gz
    Time::HiRes        CPAN: by-authors/id/DEWEG/Time-HiRes-*.tar.gz
    
    SNMP_Session       http://www.switch.ch/misc/leinen/snmp/perl
    RRD                http://ee-staff.ethz.ch/~oetiker/webtools/rrdtool
            

    Don't forget to run make site-perl-install when installing RRD!

    Modules marked with CPAN come from the Comprehensive Perl Archive Network. If you don't know where to find a CPAN site or how to install modules, take a look at the Perl FAQ here:

    ftp://ftp.cs.colorado.edu/pub/perl/CPAN/doc/FAQs/FAQ/PerlFAQ.html

    You may also be able to use CPAN.pm to quickly and easily install modules. Type "perldoc CPAN" to learn more about it. Cricket ships with a CPAN-style Bundle which should make it simple to install the modules you need. The magic command to do this is:

    % cd cricket/lib
    % perl -I. -MCPAN -e 'install Bundle::CricketPrereq'
            
  4. Choose a user to run Cricket. Many sites create a special user to run Cricket, but that's not necessary. If you choose to run Cricket from your own account understand that there will be several directories in your home directory that Cricket needs. Learn to live with them until you know how to move them elsewhere, or use a dedicated user for Cricket so that the directories won't bug you.

    Do not run Cricket as the root user. Superuser privileges are not necessary to run Cricket, and granting them would very probably create a security hole on your system.

    If you use a dedicated user for Cricket make certain that mail sent to that user ends up in your mailbox. Some of Cricket's runtime errors get reported (with cron's help) via e-mail.

    In the examples, this will be the user named "cricket".

  5. Extract the tarfile and run configure. Well, you seem to have already done this, since you are reading this file. Please make certain the expanded directory tree is in the home directory of the user that will be running Cricket. For example, if this was Cricket version 1.0.0, it would look like this:
    % cd ~cricket
    % gunzip -c cricket-1.0.0.tar.gz | tar xvf -
            

    You now need to run "sh configure" from $HOME/cricket-1.0.0:

    % cd ~cricket/cricket-1.0.0
    % sh configure
            

    This will fix the Perl scripts to work in your environment.

  6. Make a softlink to the version you are running. To make it easier to upgrade later you'll want to make a link from a generic name to the specific name you are currently running.

    To do this:

    % cd ~cricket
    % ln -s cricket-1.0.0 cricket
            

    This makes it so that you can refer to things in $HOME/cricket, and still get the version 1.0.0 copies of those files. You'll then be able to swing that link over to newer versions as they become available.

    If you don't have a cricket-conf.pl file yet, copy the example file cricket-conf.pl.sample to cricket-conf.pl, and edit it. You will need to set $gCricketHome to the home directory of the user that Cricket runs as (this is used to locate the cricket-config directory, among other things). The $gInstallRoot variable should point to where the Cricket scripts are. It is recommended to point this to the symlink, so that you can just copy this file if you upgrade Cricket later. If you followed the instructions so far, $gInstallRoot will be set correctly without changing the sample, so cricket-conf.pl will contain these two lines:

    $gCricketHome = "/home/cricket";
    $gInstallRoot = "$gCricketHome/cricket";
            

    There usually is no need to set $gConfigRoot explicitly, so leave that line commented out.

    Usually, there is no need to touch anything else in this file.

  7. Copy the sample-config tree and modify it for your site. Copy the files you intend to use from the sample-config tree from the cricket distribution to $HOME/cricket-config. You can use

    % cd ~cricket
    % cp -r cricket/sample-config cricket-config
            

    but the parts of the tree you won't be using immediately may cause some (otherwise harmless) warnings later on. If you don't copy the entire tree, make sure to at least include the top level Defaults file! Let's focus on two subtrees, routers and router-interfaces. If you can get these going, you'll be able to get others going too.

  8. Setup the routers subtree. Go into the routers tree and look at the targets file. This is where you want to tell Cricket which router to talk to.

    Note: As far as beginners are concerned, statistics are only available from Cisco routers. If you have another kind of router, you should skip to the router-interfaces step for now. Later, when you understand the system better, you can come back and use contributed configurations from other Cricket users with hardware like yours to make your routers subtree work.

    You will be editing the file "Targets" to tell Cricket about your router. You want to change these lines:

    target  engineering-router
    target-type=Cisco-7500-Router
    short-desc  =   "Router for engineering folks"
            

    You should change the words "engineering-router" in the first line to the hostname of the router you want to talk to. If it has not been assigned a hostname, you need to stop and do that (perhaps by simply editing /etc/hosts) before configuring Cricket for the first time. Cricket can talk to things via an IP address, but configuring it that way is beyond the scope of this document.

    You should change the words "Cisco-7500-Router" to reflect the kind of router you have. You can choose from this list:

    • Cisco-2500-Router
    • Cisco-3600-Router
    • Cisco-7200-Router
    • Cisco-7500-Router

    If your router type is not on this list, choose "Cisco-2500-Router" for now. You can experiment with other types later, if you want. The only difference is the amount of information you get about ambient temperature where the router is installed.

    Comment out the other target in that file (main-router) using the Cricket comment symbol "#".

    Finally, if you are not using the default SNMP community string, "public", you need to tell Cricket what community string to use. Since a community string is something that is usually shared across many network devices, it should live at a higher place in the config tree. This is a useful feature of the config tree -- it lets you move things that apply to lots of targets to a single place (higher in the config tree) where it will be easier to maintain. To set the community string for your installation, edit the the root Defaults file, which is ~/cricket-config/Default. It has a section like this in it:

    Target  --default--
      ... other stuff ...
      snmp-community      = public
            

    Change "public" to your community string. Write the file and exit.

    After you make any changes to the config tree, you need to compile it. Storing it in a compiled form makes accessing it quicker and easier. Do this:

    % ~/cricket/compile

    If the compile command give you any errors, stop at this point and fix the problem. This is a good time to check to make sure you are logged in to the Cricket user account (try out the whoami command and see what it says). Check the FAQ for more help with errors often seen in this step.

    Now, you are ready to try out your configuration. We will run the collector by hand on just this subtree first to see if there were any errors.

    % ~/cricket/collector /routers

    You should see something like this on screen, though this example was wrapped by hand for readability:

    [25-Jan-1999 15:21:20 ] Starting collector: Cricket version 0.64
                  ( Fri May 14 14:14:28 PDT 1999 )
    [25-Jan-1999 15:21:20 ] Retrieved data for engineering-router:
                  19,19,15,22,2510380,49824724
    [25-Jan-1999 15:21:20 ] Processed 1 targets in 2 seconds.
            

    You can add the arguments "-logLevel debug" on to the end of the command-line to get more information to help to solve problems.

  9. Set up the router-interfaces subtree. We will setup the router-interfaces subtree in much the same way that we set up the routers subtree. However, there's a tool to help us avoid the grunt work. This tool is called 'listInterfaces', and it comes in the util directory.

    listInterfaces one or two arguments. It must have a router name as the first argument, and it can take a community string as the second argument. If you do not specify the community string, it defaults to "public".

    When you run listInterfaces against a router, it will print a Cricket config to it's standard output. Thus, you can use it like this to save some work:

    % ~/cricket/util/listInterfaces engineering-router > interfaces

    You should check the automatically generated interfaces file and make certain it only lists interfaces you are interested in.

    By adding this file, you've just changed the config tree. Remember, you must compile the tree every time you edit it. Or rather, if you forget to compile the tree, the Cricket collector will do it for you, but in that case you are not likely to see any errors you might have introduced, so it's always a good idea to compile explicitly.

    Once again, run the collector by hand to make certain that it will be able to talk to your router and collect data. The command to do this is:

    % ~/cricket/collector /router-interfaces

    Once again, you should see that Cricket is successfully retrieving data for your targets.

  10. Run the collector from cron. Now you need to set up cron to run the collector every five minutes for you.

    The collector is usually run from a wrapper program whose job it is to handle locking, rotating log files, and other administivia.

    The wrapper is called collect-subtrees. It reads a file from the Cricket install directory called "subtree-sets". This file holds lists of subtrees which will get processed together in a group. It also lists the places where Cricket will expect to find it's configuration and log directory. As it comes in the distribution, this file needs no changes.

    Later, you will find that this file lets you control what parts of your config tree will be collected in parallel. This is a critical feature to increase the number of devices you can poll.

    You'll need to add an entry like this to cron:

    0,5,10,15,20,25,30,35,40,45,50,55 * * * * $HOME/cricket/collect-subtrees normal
            

    Usually, this is done by typing "crontab -e". In the crontab, it will all be on one line. It has been manually wrapped above for readability.

    If the script generates output, it will be sent to the user who owns the crontab. You should make certain you can see that mail. If you don't see the mail, you won't know what's wrong (though most of the messages you are likely to see will also show up in the $HOME/cricket-logs directory).

  11. Set up the Grapher. For this part of the installation, you will need an installation of Apache running which is correctly configured to let you run CGI scripts linked into your $HOME/public_html directory via a symlink. Configuring the web server correctly has proven to be the hardest for beginning Cricket users. Here are some resources that might help you get it right:

    Please do not continue until you are certain things are configured correctly. Hint: if you are using a vanilla RedHat Linux install, you are not ready to continue until you do something about suEXEC. See the Cricket FAQ for more info.

    OK, now that you have Apache (or some other web server) correctly installed, you need to make some more links.

      % cd $HOME/public_html
      % mkdir cricket
      % cd cricket
      % ln -s $HOME/cricket/VERSION .
      % ln -s $HOME/cricket/grapher.cgi .
      % ln -s $HOME/cricket/mini-graph.cgi .
      % ln -s $HOME/cricket/lib .
      % ln -s $HOME/cricket/images .
            

    These links expose the minimal amount of Cricket necessary to the web server.

    Now, try going to this URL:

    http://localhost/~cricket/cricket/grapher.cgi

    If you are running under a different user, or your webserver is on a different machine from your web browser, alter the URL accordingly. Additionally, if you want to get rid of the username reference in the URL (i.e., the ~cricket bit), using another method to start grapher.cgi, you'll have to edit grapher.cgi and uncomment the lines

    # $ENV{'HOME'} = '/path/to/cricket/home';
    # return;
            

    and replace /path/to/cricket/home with the directory you actually installed Cricket in.

    You should see the front page, including some graphics and a couple of links to more stuff. If you get a web server error page instead you MUST go check the web server error log. The answer to what went wrong will almost certainly be in there.

  12. You're done! The graphs will not show any data for a while, since it takes some time to have enough history to make an interesting graph. As long as you are certain the collector is working right (now would be a very good time to check your e-mail for errors, and scan the files in $HOME/cricket-logs for errors) then you can take some time to read the other documentation, or maybe even grab a beer.

    After about an hour, you should have some mildly interesting graphs. After a day, hopefully you'll have some very interesting graphs. After three months, you'll finally have graphs that you can show to your boss to prove that you need to upgrade the office's 384 kilobit DSL to a T3. We're pulling for you, really we are. :)

  13. The Next Step. Now that you are an expert (expert beginner, that is), you should add some more targets to your config tree, and explore the other subtrees in the sample-config tree. With those subtrees, you can monitor web server performance, switch port usage, and other interesting stuff.

    As you learn more, you'll be able to make your own subtrees to handle special kinds of data unique to your site. If you make a subtree that can support a device others are using, please submit to the Cricket contributed configurations site, which is part of Cricket's homepage at http://cricket.sourceforge.net.

Cricket version 1.0.5, released 2004-03-28.

Copyright (C) 1998-2000 Jeff Allen. Cricket is released under the GNU General Public License.

cricket-1.0.5/doc/copytosql.html0100644000114300000000000001017407760440274015614 0ustar bertdwheel copy-to SQL

Using copy-to for Data Warehousing

This document assumes the following: you have some knowledge about setting up databases, you have a database available, and have some sort of administrative access on it.

Introduction

Until now there has been no good means for data warehousing data from Cricket. RRD provides a great means for graphing data but sometimes there is a need to have the numeric values in a more accessible form. That's where the SQL method for copy-to comes in.

Set-Up

This is still in it's early stages of development and has only been tested under SQLServer 7.0. This should definitely work with other versions of SQLServer, and maybe Sybase.

You need to create a database called 'cricket'. In this database, set up a table called 'CricketData'. There is a file in the docs directory called cricket.sql which can be used either to create the table, or as a reference to manually create the tables if it won't work with your database.

Using

In a target definition create a copy-to:

target myImportantDatasource

copy-to = "sql:dbi:Sybase:NMSDB,mylogin,mypassword"

As you can see, it's pretty simple to define a copy-to. The dbi:Sybase:NMSDB can be customized to use whatever special definitions your DBI driver requires. This is designed to be as flexible as possible to allow for working with all DBI drivers.

When the collection happens, it inserts into the database whatever numbers were retrieved from the target.

That's it! You now have a crude form of data warehousing set up for Cricket data.

Caveats

Yes, there are many.

  • This isn't guaranteed to work with all DBI platforms. While it's designed to be as flexible and generic as possible, it may not work with your platform. If it doesn't, we're accepting patches. :)
  • Cooked numbers aren't inserted. This inserts every number into the database as-is. This means that all of the cool things rrd can do like averaging, interpolation, and computations are gone. This copy-to happens before anything is done with RRD.
  • Datasources aren't named. This is a big limitation right now. It means that the user has to figure out what datasource number co-responds to which counter. Luckily, it goes in the order collected in the Defaults file for the particular target. Until Cricket can name datasources, it can't insert named datasources into the database.
  • Cricket doesn't supply any tools for using these numbers. It's up to you to provide tools for extracting and using the data once it goes to a database. Cricket is simply an agent for passing the numbers over.
  • Still in early stages of development. Don't expect perfection. As more people use this, we'll see greater improvements, though.

Cricket version !!VERSION!!, released !!RELDATE!!.

Copyright (C) 1998-2000 !!COPYRIGHT!!. Cricket is released under the GNU General Public License.

cricket-1.0.5/doc/cricket.sql0100644000114300000000000000177707367641065015053 0ustar bertdwheel/* This will create a table for use in storing Cricket data. This must be run * in a database called 'cricket' for the collector script to run properly. * * Set it up with your own size parameters. Cricket doesn't insert a large * amount of data and this is really only good for warehousing as there is * no kind of calculations or interpolation done - data is simply dumped from * Cricket into the DB. * * This will work with Microsoft SQLServer. I do not know if it'll work with * Sybase or any other type of database. If you have a version for another DB * please submit a patch so we can integrate it. */ CREATE TABLE [dbo].[CricketData] ( [targetPath] [varchar] (256) NOT NULL , [targetName] [varchar] (256) NOT NULL , [ds] [int] NOT NULL , [value] [int] NOT NULL , [id] [int] IDENTITY (1, 1) NOT NULL , [TimeStamp] [datetime] NOT NULL ) ON [PRIMARY] GO ALTER TABLE [dbo].[CricketData] WITH NOCHECK ADD CONSTRAINT [IX_CricketData] UNIQUE NONCLUSTERED ( [TimeStamp], [ds], ) ON [PRIMARY] GO cricket-1.0.5/doc/gpl.html0100644000114300000000000004417310031605175014333 0ustar bertdwheel GNU General Public License
        GNU GENERAL PUBLIC LICENSE
           Version 2, June 1991

 Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc.
 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

          Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

        GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

    a) You must cause the modified files to carry prominent notices
    stating that you changed the files and the date of any change.

    b) You must cause any work that you distribute or publish, that in
    whole or in part contains or is derived from the Program or any
    part thereof, to be licensed as a whole at no charge to all third
    parties under the terms of this License.

    c) If the modified program normally reads commands interactively
    when run, you must cause it, when started running for such
    interactive use in the most ordinary way, to print or display an
    announcement including an appropriate copyright notice and a
    notice that there is no warranty (or else, saying that you provide
    a warranty) and that users may redistribute the program under
    these conditions, and telling the user how to view a copy of this
    License.  (Exception: if the Program itself is interactive but
    does not normally print such an announcement, your work based on
    the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

    a) Accompany it with the complete corresponding machine-readable
    source code, which must be distributed under the terms of Sections
    1 and 2 above on a medium customarily used for software interchange; or,

    b) Accompany it with a written offer, valid for at least three
    years, to give any third party, for a charge no more than your
    cost of physically performing source distribution, a complete
    machine-readable copy of the corresponding source code, to be
    distributed under the terms of Sections 1 and 2 above on a medium
    customarily used for software interchange; or,

    c) Accompany it with the information you received as to the offer
    to distribute corresponding source code.  (This alternative is
    allowed only for noncommercial distribution and only if you
    received the program in object code or executable form with such
    an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

          NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

         END OF TERMS AND CONDITIONS

  Appendix: How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) 19yy  <name of author>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

    Gnomovision version 69, Copyright (C) 19yy name of author
    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Library General
Public License instead of this License.

Cricket version 1.0.5, released 2004-03-28.

Copyright (C) 1998-2000 Jeff Allen. Cricket is released under the GNU General Public License.

cricket-1.0.5/doc/history.html0100644000114300000000000001731310031605175015246 0ustar bertdwheel The History of Cricket

The History of Cricket

By Jeff Allen

Cricket did not spring, fully formed, from my fingers late one night. Granted, most of it did come about late at night, but that's just the way I work. Cricket is the result of using and in some cases being limited by MRTG, all the while getting more and more addicted to it.

RRD Tool is Born

In January 1998, Tobias Oetiker released some of the first versions of the RRD tool. I took a look at this work and immediately recognized that half of my problems with MRTG would be solved by switching to using RRD as the backend, instead of rateup, the old backend for MRTG. RRD was designed to be much more flexible and much higher performance than rateup. However, RRD did not promise to make any dent at all in the complexity of our MRTG configuration, nor was it immediately obvious how we could extend MRTG to take advantage of RRD's new flexibility.

The Config Tree is Born

During the Spring of 1998, Tobias continued to work on RRD, but little work was being done to integrate it into a future version of MRTG. There are only so many hours in a day, after-all, and Tobias does need to earn a living too. I kept thinking about the problems facing WebTV, and how we could best put to use the really neat RRD tool while solving our config file (and, to a lesser extent, parallelization) problems. The answer was the config tree. After writing a small proof of concept, I sold my bosses on paying a contractor (Emmett Hogan) from Global Networking and Computing (http://www.gnac.com) to implement the design for an RRD frontend using the config tree idea. This project was to be a six week job, starting around July, 1998. This new frontend for RRD came to be called Super MRTG, or SMRTG.

As is often (almost always?) the case, it was very hard to predict how much time would actually be required to do the job. During Emmett's work, we learned a bunch more about how we wanted the final system to work. Emmett's finished product was still not ready to replace MRTG here at WebTV, and we were not ready to release it to the world. I took over SMRTG and added the features we needed to put it into production at WebTV. I also released a few versions of SMRTG to the MRTG-developers mailing list for review.

About this point a number of things happened. First, I realized that I was really unhappy with the name SMRTG for several reasons. It was too cumbersome, the project is only tangentially about routers at this point, and there was not a line of MRTG code in SMRTG -- it only looked the same. Thus, we needed a new name. I called it Cricket because it popped into mind. It's short and catchy, and there seems to be no other piece of network management software called Cricket. A very long time ago, there once was a really cool piece of graphing software for the Mac called Cricket Graph. That might have tickled my subconscious, though I only remembered it once people started referring to the output of Cricket as "Cricket Graphs".

Also, Tobias and I talked about Cricket's, RRD's, and MRTG's futures. We agreed that Cricket and RRD made good partners, but that RRD is useful on it's own too. (Thus, RRD and Cricket will be distributed separately for the foreseeable future.) We also agreed that it made no sense for him to duplicate my effort on the frontend, so it's unlikely there will ever be a version of MRTG that uses RRD as it's backend. Instead, we will try to help people convert to using Cricket if they need the performance of RRD. (And who doesn't!) As you can see from this history, Cricket would not exist if it wasn't for MRTG and RRD, two of Tobias' ultra-cool inventions. I am deeply indebted to that giant for letting me stand on his shoulders.

Cricket was unveiled to the public as part of Usenix's First Conference on Network Administration. I submitted a paper on Cricket there, and released a version that people started to rely on. During the rest of 1999, the Cricket-users mailing list slowly grew from 100 through 400. During 1999 Cricket changed little, even though lots of users submitted patches. That was mostly due to me getting a new job and Cricket losing some of it's novelty value for me.

I gave an invited talk at Usenix's LISA 99 which featured Cricket, along with other tools I have worked on at WebTV. At LISA, I was invited to give a talk on Cricket at SANE 2000. Word of Cricket spread, and it was written up in both Sysadmin Magazine and WebReview.com.

Around January 2000, I decided that the Cricket users deserved to have better support, and what better way to do that than to turn the project over to the community at large? I got a project on Sourceforge and worked, slowly, through the spring of 2000 to move things over. My goal was to release 1.0.0 in time for the talk at SANE 2000. More importantly, my goal was to setup a CVS tree that would allow us to actively develop Cricket going forward. I seem to have succeeded. My co-worker Adam Meltzer has already been working hard at adding features to the 1.1.x branch, which will be ready for beta tests this summer.

Today, in May 2000, Cricket is installed on three separate servers at WebTV and is monitoring about 9000 targets, with over 50 different target types represented. Cricket takes data from SNMP, shell scripts, and files. Data gathered via scripts comes in via syslog, application-specific event streams, HTTP, rcp, and 2400 baud serial. WebTV operations folks know that if they want to know the health of something they should ask Cricket, and if it's not there, they should ask "Why Not!?!". During peak times, Cricket has time and again given us unprecedented visibility into the health of the WebTV Service. It has paid for itself, and we genuinely hope it will be as productive at your site.

Cricket version 1.0.5, released 2004-03-28.

Copyright (C) 1998-2000 Jeff Allen. Cricket is released under the GNU General Public License.

cricket-1.0.5/doc/index.html0100644000114300000000000001027510031605175014654 0ustar bertdwheel Cricket Documentation

Cricket Documentation

Support

Detailed (and up-to-date) information about how to get help with Cricket is available from the Cricket website's support section. In particular, you might like to read the FAQ.

Cricket version 1.0.5, released 2004-03-28.

Copyright (C) 1998-2000 Jeff Allen. Cricket is released under the GNU General Public License.

cricket-1.0.5/doc/inst-mapping.html0100644000114300000000000001703010031605175016147 0ustar bertdwheel Understanding Instance Mapping

Understanding Instance Mapping

Cricket has a nifty feature called instance mapping. Basically it's meant to take the busywork out of monitoring SNMP devices. In this document, you'll learn far more than you ever wanted to know about it.

The Problem

In the SNMP world, when something is stored in a table, each table row has a number. These numbers, called instance numbers, are used to reference the rows. Since routers typically have lots of interfaces, they are stored in a table, indexed by these instance numbers. I can ask a router to give me the traffic measurements from row number 4, and it will know what I mean and go do it. However, when we humans think about router interfaces, we don't think about instance numbers. We think about some other identifier for the row, like the interface name or the IP address.

Interface mapping lets you configure Cricket to talk to devices based on one of the other keys in this table. The mapping part comes in when Cricket turns the key you give it into an instance number. It does this in an efficient way, which is guaranteed to be right no matter what kind of row re-arranging goes on on the router. (It seems cruel, but they are allowed to re-arrange their rows at any time. This would presumably not be part of what makes SNMP "simple".)

What does this mean to me?

Nothing, as long as you are using the sample config tree's router-interface subtree. It comes ready to automatically do interface mapping using the interfaces name as a key. When you use listInterfaces to make an interfaces file, it will set the "interface-name" tag for you, and Cricket will use that to do the instance mapping automatically.

On the other hand, if you want to setup Cricket to use a different key (say, the IP address, or maybe the human-readable descriptions Cisco routers support), or to monitor something else via SNMP that lives in tables, then you need to read on and understand the map dictionary, and how it is used to control the interface mapping algorithm.

The Icky Details

When Cricket comes across an instance number which looks like map(interface-name), then it knows that instead of using a hard-coded instance number, or an instance set, it will be using the instance mapping code to find the instance number. It uses the part inside the parenthesis ("interface-name" in the case above) to find an entry in the map dictionary that it can use to help map the given key to an instance number.

There are two interesting tags in a map entry that control this process. The first is the baseOID, which tells Cricket which column of which table it is going to be looking through to find the match. For the interface-name map, this is set to "ifDescr". Cricket will look up the name "ifDescr" in the OID table, and will use the resulting OID as the base-oid for an SNMP walk of the table. The other interesting tag is match, which is a string that the fetched column will be compared to. This string is expanded with respect to the target dictionary, so you can use the "%tag%" syntax there. In the sample config, we compare the ifDescr column of the interface table to "%interface-name%", which gets expanded to the current value of the the tag interface-name in the current target. If the string starts and ends with the slash character, then it is used as a Perl regular expression and a case-insensitive RE match is done, instead of a simple string match. Use this feature only when necessary. A string match is preferable, since it has less overhead.

The system is efficient and accurate. It uses a file sitting right next to your RRD file to store the last-used index (avoiding gratuitous table-scans) and uses an internal cache to minimize table-scans, should they be necessary. It is accurate because every time Cricket fetches data using a cached instance number, it also fetches the key in the same packet. If the key no longer matches, then the cache is invalidated, a table-scan is done to map the instance number, and the data is re-fetched using the new instance number. The maximum cost of this system is one table-scan per host anytime an instance changes, and one extra variable fetched per target.

Instance mapping is only enabled when the inst tag starts with "map(". This means that hard-coded instances and instance ranges are still completely supported, if you are not ready to use the instance mapping feature.

The Ickiest Details

The SNMP queries done by the instance mapping algorithm are implemented semi-independently from the rest of Cricket's datasource management code. This is partly because it was easier to implement that way, and partly because instance mapping is an intrinsically SNMP-thing, and it probably will never make sense to talk about using instance mapping with an exec datasource.

When it comes time to do a table scan, the instance mapping code uses the tag named snmp from the current target to find the community string, host, and port for the SNMP transaction. As discussed above, the snmp tag, by convention, has the community string, host, and port in it in a format ready to plug into SNMP ds-sources. Thus, even if you've come up with a different way to encode community string, host, and port into your datasources, you still need to have a tag named snmp for the instance mapping code to use.

The moral of the story is to try very hard to not mess with the sample-config tree. (The quote "Though this be madness, let there be a method in it." comes to mind...) It's setup the way it is for a reason, and though you may be able to make it more efficient in some ways, you won't end up with a config tree that is as flexible and broadly-applicable. Thus, I might do something later which breaks your config tree without meaning to do so. And I won't feel very sorry about it, now that you know better.

Cricket version 1.0.5, released 2004-03-28.

Copyright (C) 1998-2000 Jeff Allen. Cricket is released under the GNU General Public License.

cricket-1.0.5/doc/intro.html0100644000114300000000000003323110031605175014675 0ustar bertdwheel A Gentle Introduction to Cricket

A Gentle Introduction to Cricket

Cricket was designed to solve several weaknesses WebTV found in MRTG when we tried to use it with thousands of targets, and for many different purposes. We found that the more we customized the MRTG config file, the larger and more unwieldy it got. We found that we were making mistakes due to the complexity, and then we wrote scripts to write the configs for us, and using these scripts, we made mistakes in a faster, more automated manner. Something needed to be done.

Cricket uses a hierarchical configuration system, thus a complete set of Cricket config files is called a "config tree". Configuration information that will be used again and again can be stored high in the tree, and inherited by all the leaves. More specific information can be stored closer to where it is used, but still in one place (instead of each place it is used). All the way down to the leaves of the tree, information from higher up can be overridden. Files are grouped into directories and processed in a predictable order within those directories. As a directory is processed, the state of the system is saved and restored. In this way, changes made to the defaults in a sub-tree do not affect a sibling sub-tree.

Understanding the config tree

Understanding the config tree is critical to understanding how to use and modify Cricket. Everything Cricket knows it learns from the config tree. That includes things like which variables to fetch for a certain type of device, how to fetch those variables, which devices to talk to, and what type of device they all are. The inheritance property of the config tree applies equally to all types of data, making it possible to make a concise description of a large, complicated set of monitored devices.

A guided tour

In this section, we will take a guided tour of the sample config tree that ships with Cricket. It would be a very good idea to read this section with a window open that you can use to explore the sample config tree.

The first thing to notice about the config tree is that every directory has a file in it called "Defaults". This file is not strictly speaking necessary, but you will find it in nearly every level of every config tree. It's purpose is to hold settings that will apply to the entire subtree that it sits on top of. Thus, when you look at the file named sample-config/Defaults, it's important to realize that unless those values get overridden later, they will be used throughout the entire config tree.

When Cricket goes to process a directory, it processes the Defaults file first (if it exists), then it processes each ordinary file, and finally it processes each of the directories. When processing a directory, it saves the current configuration before entering it, and restores the configuration after leaving the directory. Note that Cricket does not save and restore the configuration when processing files; one file could make a change to the defaults that another file in the same directory can see. For the time being, consider this a feature. It can be useful, as long as you are expecting it to behave that way.

Scroll through the root Defaults file and take a look at the sections. You'll see that each chunk of the file begins with a certain word (like "target", or "oid"). After that word, they differ somewhat, but generally what a chunk does is define some tag/value pairs, and assign them to some key name. For instance, the tag rrd-datafile gets set to "%dataDir%/%auto-target-name%.rrd".

That's great, you say, but what are the percent signs about? This brings us to expansion. Before a dictionary is used, it is expanded. This means that variables which are referenced with the "%tag%" syntax are replaced with their actual values. If the value also has a variable in it, it is also expanded. (There is no check for loops so don't accidentally make one!) This is a very powerful feature which makes complicated configurations boil down to a few simple config lines.

For instance, the example I chose above sets the tag rrd-datafile to a proper filename made up of the data directory, the target name, and the extension ".rrd". But you'll notice that dataDir is itself defined in terms of some other tags. As long as all the tags eventually map to some text, the expansion process turns this mess into a complete pathname. If a tag is not defined, but it is referenced via an expansion, then Cricket will log a warning, but it will attempt to continue to use the partially expanded string.

The tags that Cricket uses to get it's configuration are listed below in the reference section, one at a time with a description. All other tags that appear in the dictionaries in a config tree are either in use by expansions, or ignored. For instance, nowhere in the reference section will you find dataDir mentioned. That is simply a tag that exists to make the definition rrd-datafile easier to read. You can add as many of your own tags as you want; it's all up to how you want to setup your config tree.

After Cricket expands a string, it scans the string for substrings surrounded by curly braces ({like this}). These substrings are passed to Perl's eval() function, which means you can do arbitrary math and other nifty trickery between curly braces. Some day I'll add more examples for how you might use this, but for now all I'm telling you is that it's there. You have to figure out how to use it.

Target types, and datasources

Now, let us take a digression for a second to talk about target types, and datasources. A datasource is something you want to graph. For instance, "router inlet temperature", or "inbound octets per second". Datasources that all relate to the same type of device are grouped together into a target type. A target is a distinct device from which you will be collecting data. Every target has a target type, which is how Cricket knows what data to fetch, and how to fetch it. This is all described in much more detail below, but for now, you already know enough to get going. This is because Cricket's sample config tree comes with lots of predefined target types that will let you look at common things on your network.

To see this stuff in action, go into the sample-config/routers directory and take a look at the Defaults file. What this file is doing is setting things up so that if you create a target of type "Cisco-7200-Router", Cricket will know what data to fetch. As you can see by the different target types defined in this file, not all routers are created equal. Some can return temperature, some can't.

Tacking a Target

Now, let's check out a target definition. Note that we've set a number of tags in the root Defaults file and in the subtree Defaults file related to the target named --default--. This special target is never used to fetch data. Instead, it is used as kind of a skeleton for all future targets that are created in the subtree below that point. Take a look inside the file named Targets. This file defines some real targets. The sample config tree comes with two, engineering-router and main-router. These two dictionaries are added onto the bottom of the config tree, and are sometimes referred to as leaves. A leaf is where some work happens.

When Cricket comes to process the engineering-router leaf node, the first step is to build a working copy of the dictionary. Clearly the information in the leaf node is not enough to collect any data from the router. For one thing, we don't know what the hostname of the router is, so we don't know where to send the SNMP packets. This is where the --default-- dictionaries come in. To make the complete working dictionary for this part of the config tree, Cricket traverses all the nodes from the root down to the current node, gathering together the --default-- dictionaries. Items lower in the config tree override items higher up.

Cricket uses the target-type tag to decide what method to use to fetch the data. If does this by finding a targetType dictionary that matches the target-type tag for the current target. This targetType dictionary has a tag named ds, which is a list of the datasources we expect to fetch from this router. Each datasource is fetched in the same way, and there Cricket discovers that it will be talking SNMP to this router, in order to fetch 6 variables.

Next, Cricket turns over control to the SNMP fetcher. The fetcher is trying to turn an SNMP URL into a measurement. The SNMP URL for the data on the engineering-router is snmp://public@engineering-router:161/cpu1min. But you've probably already noticed that the only thing the fetcher seems to have to go on is the ds-source tag, which has a %snmp% where we expected to find the community string, hostname and port.

How does Cricket manufacture the whole SNMP URL for this target?

Explaining expansion

Immediately after Cricket makes the working dictionary for a target it is working on, it fully expands the target with respect to itself. Likewise, when Cricket fetches a dictionary like the cpu1min datasource dictionary, it is also expanded with respect to the target dictionary. By the time the SNMP fetcher is looking for the contents of the ds-source tag, it has already been expanded to the correct value.

Let's unwind the expansions that contributed to changing %snmp% into public@engineering-router:161.

From sample-config/Defaults
      snmp-community      = public
      snmp-port           = 161
      snmp                = %snmp-community%@%snmp-host%:%snmp-port%
From sample-config/routers/Defaults
      router              = %auto-target-name%
      snmp-host           = %router%
        

You can see that one level about the leaf, the snmp-host tag gets set (via the router tag) to the name of the current target. Tags starting with auto- are provided to you by Cricket. There are a number of these automatic tags which are discussed below. The rest of the snmp tag is filled in by defaults inherited from all the way up at the top of the config tree.

This is a good example of the power of the config tree. If you were asked to monitor a new piece of network hardware which had a slightly different community string, you could put a snmp-community tag in the target's dictionary and override the one normally inherited from above. Alternatively, if you need to change the community string on 1000 devices simultaneously, you could do so by simply changing it at the root, then allowing the change to filter down to each of the 1000 targets in your config tree.

Cricket version 1.0.5, released 2004-03-28.

Copyright (C) 1998-2000 Jeff Allen. Cricket is released under the GNU General Public License.

cricket-1.0.5/doc/modperl.html0100644000114300000000000000703610031605175015210 0ustar bertdwheel Using Cricket with mod_perl

Using Cricket with mod_perl

mod_perl is an Apache module that allows you to run Perl scripts inside of Apache without forking a new process for each one. This saves lots of time, since Perl only needs to parse the script once and can execute it a number of times.

As of version 0.68, Cricket can run under mod_perl. Typically, no special code needs to be added to scripts to make them work in the mod_perl environment, but Cricket's lazy use of namespaces required some fix up work. Mea culpa. That work has been done now, and going forward, I intend to make sure Cricket works under mod_perl.

If you'd like to use Cricket under mod_perl, you need to first fetch and install Apache with the mod_perl module. Cricket has only been tested so far with Apache 1.3.6 and mod_perl 1.19. The best way to build and install Apache and mod_perl is to fetch both packages, uncompress and untar them into one directory, then go into the mod_perl directory first and follow the directions in the INSTALL.apaci file.

Once you have an installation of Apache with mod_perl installed, you need to add lines like the following to your httpd.conf file:

    <Files *.cgi>
      SetHandler perl-script
      PerlHandler Apache::Registry
      Options ExecCGI
    </Files>

These lines tell your webserver that whenever it sees a file ending in .cgi, it should let the embedded Perl interpreter run it. This, of course, is problematic if you have some .cgi files on your webserver that cannot be run by Perl. In that case, you should use a more restrictive specification than "*.cgi" to limit the scope of the directives to files in the Cricket user's home directory.

There seems to be a bug which causes Apache to fail to find a library file in some cases. Confusingly, this tends to happen when it's trying to handle a URL that points to an image, resulting in missing images. This is strange, since serving up images should not require the Perl interpreter. You can fix this by setting the PERL5LIB environment variable to the Cricket library directory before starting the Apache webserver.

If you are going to be editing the library files (i.e. to add a new feature), you'll want to definitely add the "PerlInitHandler Apache::StatINC" directive to your config file. This will make mod_perl check library timestamps and reload/reparse them when necessary. You should not run a production server with this enabled, since it does some extra work each time a library is used, which could be avoided. If you are not using the StatINC module, you will need to restart your server anytime you change a library file, or else the changes will not be picked up.

Cricket version 1.0.5, released 2004-03-28.

Copyright (C) 1998-2000 Jeff Allen. Cricket is released under the GNU General Public License.

cricket-1.0.5/doc/monitor-thresholds.html0100644000114300000000000006573710016702463017427 0ustar bertdwheel Monitor Thresholds

Understanding and Utilizing Cricket Monitor Thresholds

Although designed as a real-time data collection and trending tool, real-time alerts (or alarms) are a natural extended functionality for Cricket. Unfortunately, because they are not a part of the core design, the alert mechanisms in Cricket are not as cleanly implemented or efficient as they could be. If your interest is purely in a tool to generate real-time alerts, then Cricket is probably not the best choice. But if you already utilize Cricket for data collection and real-time trend analysis and you have the additional need for some light real-time alerting mechanism, then Cricket can meet your needs.

In Cricket, the alert mechanism is called a monitor threshold. Monitor thresholds are set (or enabled) for specific data sources through the monitor-thresholds target dictionary tag. After the data collection pass, Cricket processes each monitor threshold by retrieving the most recent value of a data source from the RRD file and applying some criteria specific to the monitor threshold type. This criteria generates either a pass or fail condition. Depending on the setting of the persistent-alarms tag for the target, Cricket executes a specified action.

Note that the most recent value of a data source from the RRD file will not necessarily agree with the most recent value fetched from by the collector because RRDtool interpolates.  For those familiar with RRD tool internals, the "most recent value" is retreived from the first RRA in the file with a consolidation function of AVERAGE. The order of RRAs in the file is specified by the rra tag in the targetType dictionary.

Note that a monitor threshold configured for a multi-instance (aka vector instances) target will be checked and an action possibly executed for each instance. Monitor thresholds are not supported for multi-targets (as multi-targets are purely a construct of the Cricket grapher).

Syntax

monitor-thresholds = "<monitor-threshold> [, <monitor-threshold> ...]"

<data source> := a data source defined for the target; Case sensitive.

<monitor type> := One of the six supported types: exact, value, relation, hunt, quotient, or failures. Case insensitive.

<monitor type args> := a colon-delimited list of arguments specific to each monitor type. Case sensitive

<ACTION> := One of six supported actions: SNMP, MAIL, EXEC, FUNC, META or FILE. Case insensitive.

<action args> := a colon-delimited list of arguments specific to each action. Case sensitive in most cases.

<SPAN> := Spanning keyword: SPAN. Case insensitive.

<span-length> := Number of time spans a thresholds should fail before triggering an action. Number.

Format Examples

Please consider these as examples on using monitor thresholds, not best practices.

target --default--
    mail-pgm = /usr/bin/mailx
    trap-address = 127.0.0.1
    persistent-alarms = true

target network-link-1
    monitor-thresholds =
    "ifInOctets : value : n : 250000 : SNMP,
    ifInOctets : quotient : >80pct : : %rrd-max-octets% : SNMP,
     ifInOctets : relation : <10 pct : : : 300 : MAIL :  %mail-pgm% : me\\\@mydomain.com,
     ifInErrors : quotient : 0.1 pct : : ifInUcastPackets : SNMP"

target pop-2
    persistent-alarms = false
    monitor-thresholds = "users : hunt : 40 : pop-1 : users : FILE : /var/log/cricket-alerts"

target router-chassis
    persistent-alarms = false
    monitor-thresholds = 
    "cpu1min : value : n : 60 : META : router-cpu : yellow,
     cpu1min : value : n : 90 : META : router-cpu : red : SPAN : 3,
     mem5minUsed : quotient : >60pct : : processorRam: META"

Note: Make sure to include spaces/tabs leading each line related to a target as above. Or else Cricket will not process the line.

Explaining the Examples

target network-link-1
    monitor-thresholds =
    "ifInOctets : value : n : 250000 : SNMP,
    ifInOctets : quotient : >80pct : : %rrd-max-octets% : SNMP,
     ifInOctets : relation : <10 pct : : : 300 : MAIL :  %mail-pgm% : me\\\@mydomain.com,
     ifInErrors : quotient : 0.1 pct : : ifInUcastPackets : SNMP"

The first target, network-link-1, has three monitor thresholds.

  • The first generates an SNMP trap whenever the utilized bandwidth, ifInOctets, exceeds 2 Mbps (2000000bits /8 = 250000 octets). It is important to note that all entries defined in the --default-- section will be inhereted by each target. In this case, persistent-alarms = true, has a direct impact when the action will be executed.
  • The second generates an SNMP trap whenever the utilized bandwidth, ifInOctets, exceeds 80% of it's maximum capacity, %rrd-max-octets%. %rrd-max-octets% is a cricket variable that is replaced at runtime with it's real value.
  • The third monitor threshold, checks to see if the current bandwidth, ifInOctets, has a value that is within 10% of the value recorded for the last interval (300 seconds ago; assuming an rrd-poll-interval of 300 seconds). It computes abs(ifInOctets_now - ifInOctets_then)/ifInOctets then and compares this with 10% (0.1). If traffic levels have increased more than 10% over the interval, it invokes mailx to send a mail message to me@mydomain.com (note the escaped backslash and escaped '@'). This action will also be executed everytime the threshold is crossed due to the inherited persistent-alarms.
  • The fourth monitor threshold checks to see if input errors, ifInErrors, exceed 0.1% of input packets, ifInUcastPackets. If errors exceed this threshold, Cricket generates an SNMP trap.
target pop-2
    persistent-alarms = false
    monitor-thresholds = 
     "users : hunt : 40 : pop-1 : users : FILE : /var/log/cricket-alerts"
   

The second target, pop-2, has a single monitor threshold.

  • Cricket will append an entry to the file /var/log/cricket-alerts when a non-zero number of users, popUsers, are on pop-2 yet pop-1 has not reached 40 users. Once pop-1 reaches 40 users, or  pop-2 returns to a zero user count, the entry will be cleared from the file. A target can always redefine a variable that was set in the --default-- section or inhereted from lower in the config tree. In this case persistent-alarms are reset to the default value of false. Hence, the FILE action will be executed to set the alarm when the alarm condition is first detected and once to clear the alarm when the alarm condition is cleared.
target router-chassis
    persistent-alarms = false
    monitor-thresholds = 
    "cpu1min : value : n : 60 : META : router-cpu : yellow,
     cpu1min : value : n : 90 : META : router-cpu : red : SPAN : 3,
     mem5minUsed : quotient : >60pct : : processorRam: META"
   

The third target, router-chassis, has a three monitor thresholds.

  • The first two make use of the value monitor type to establish thresholds on cpu usage. More than one identical monitor type can be applied against the same datasource. In both cases, the META action is called with different arguments.
  • In the second value monitor, the keyword SPAN indicates there is an additional condition where the monitor threshold will only trigger an alarm when three consecutive monitor threshold tests fail. At the first threshold test pass, the alarm will be cleared.
  • The last monitor type will calculate mem5minUsed datasource divided by the processorRAM datasource and if the percentage is greater than 60% it will trigger a META action. Note that no additional arguments have been added to the META action, this is by design, read the Actions section for details on how to use this and other actions.

Persistent Alarms

By default, the target tag persistent-alarms is set to false. With this setting, the first time a monitor threshold criteria fails, the action is executed.  Specifically, the Alarm() subroutine in the Monitor.pm module is invoked; the action and its arguments are passed as arguments. If the criteria continues to fail (at subsequent data collection passes), the action is not executed again. After one or more failures, the first time the monitor threshold criteria passes, the action is executed. In this case, the Clear() subroutine in the Monitor.pm module is invoked, with appropriate action and action arguments.  Thus the default behavior is like a switch that toggles states when the result of the monitor threshold criteria changes.

If the target tag persistent-alarms is true, the action is executed (the Alarm subroutine is invoked) every time the monitor threshold criteria fails. An action (and Clear subroutine) is still executed once the first time the criteria passes after a string of failures. With persistent-alarms set to true, monitor threshold behavior is like a bell. It keeps ringing until the problem stops.

Monitor Types

The monitor type determines the criteria used to check a monitor threshold.

exact :

These monitors are the simplest to use and configure, and allow you to monitor a datasource for an exact match. This is useful in cases where an enumerated (or boolean) SNMP object instruments a condition where a transition to a specific state requires attention. For example, a datasource might return either true(1) or false(2), depending on whether or not a power supply has failed. The exact monitor expects one argument; the value on which the monitor will trigger. For example, monitor-thresholds = "dsPowerFail:exact:1" would cause Cricket to send a trap when the last value of the "dsPowerFail" datasource in the RRD file for this target is 1.

    dsPowerFail : exact : 1 : <ACTION> : ...
    dsTempAlarm : exact : 1 : <ACTION> : ...
  

value :

The next simplest monitor type, value monitor thresholds take two arguments, a minimum and maximum value. If the data source strays outside of this interval, the monitor threshold criteria fails. To omit the minimum or maximum value, use the character "n".

    temperature : value : 30 : 90 : <ACTION> : ...
    ifInOctets : value : n : 250000 : <ACTION> : ...
 

relation :

Relation monitor thresholds are very flexible. A relation monitor considers the difference between two data sources (possibly from different targets), or alternatively, the difference between two temporally distinct values for the same data source. The first data source is the data source for which the relation monitor threshold is defined. The difference can be expressed as absolute value, or as a percentage of the second data source (comparison) value. This difference is compared to a threshold argument with either the greater than or less than operator. The criteria fails when the expression (<absolute or relative difference> <either greater-than or less-than> <threshold>) evaluates to false. The four colon-delimited arguments for a relation monitor are:

  1. The threshold number, optionally preceded by the greater than (>) or less than (<) symbol, and optionally followed by the string "pct". If omitted, greater than is used by default and the expression, difference > threshold, is evaluated.  "<10 pct", ">1000", "50 pct", and "500" are all examples of valid thresholds.
  2. The name of the comparison target. The comparison target must either share the same path with the first target or be fully-qualified. This argument is optional and if omitted the first target is also taken as the comparison target.
  3. The name of the comparison data source, variable, integer or floating point value. In the case of a data source it must belong to the comparison target. This argument is optional and if omitted the monitor threshold data source name is also taken as the comparison data source name. Integer and floating point values can be signed or unsigned. ie: -1.05, 5, 10.1, +5
  4. The temporal offset in seconds to go back in the RRD file that is being fetched from for comparison. Note that a data source value must exist in the RRD file for that exact offset. Choose a multiple of the RRD file step size. This argument is optional and if omitted, it is set to 0.

quotient :

Quotient monitor thresholds are similar to relation monitor thresholds, except that they consider the quotient of two data sources, or alternatively, the same data source at two different time points. For a quotient monitor threshold, Cricket computes the value of the first data source as a percentage of the value second data source (such as 10 is 50% of 20). This percentage is then compared to a threshold argument with either the greater than or less than operator. The criteria fails when the expression (<percentage> <either greater-than or less-than> <threshold>) evaluates to true. The four colon-delimited arguments for a quotient monitor are:

  1. The threshold number, optionally preceded by the greater than (>) or less than (<) symbol followed by the string  "pct". If omitted, greater than is used by default and the expression, difference > threshold, is evaluated.
  2. The name of the comparison target. The comparison target must either share the same path with the first target or be fully-qualified. This argument is optional and if omitted the first target is also taken as the comparison targete.
  3. The name of the comparison data source, variable, integer or floating point value. This data source must belong to the comparison target. This argument is optional and if omitted the monitor threshold data source name is also taken as the comparison data source name. ie: -1.05, 5, 10.1, +5
  4. The temporal offset in seconds to go back in the RRD file that is being fetched from for comparison. Note that a data source value must exist in the RRD file for that exact offset. Choose a multiple of the RRD file step size. This argument is optional and if omitted, it is set to 0.

hunt:

The hunt monitor threshold is designed for the situation where the data source serves as an overflow for another data source; that is, if one data source (the parent) is at or near capacity, then traffic will begin to appear on this (the monitored) data source.  One application of hunt monitor thresholds is to identify premature rollover in a set of modem banks configured to hunt from one to the next.  Specifically, the criteria of the hunt monitor threshold fails if the value of the monitored data source is non-zero and the current value of the parent data source falls below a specified capacity threshold. The three colon-delimited arguments for a hunt monitor are:

  1. The threshold of the parent data source. Generally this should be slightly less than the maximum capacity of the target.
  2. The name of the parent target. The parent target must either share the same path with the monitored target or be fully-qualified. This argument is optional and if omitted the monitored target is also taken as the parent target.
  3. The name of the parent data source. This data source must belong to the parent target. This argument is optional and if omitted the monitor threshold data source name is also take as the comparison data source name.

failures:

The failures monitor threshold is integrated with aberrant behavior detection in RRDtool. This monitor checks the FAILURES RRA for the target and datasource. If the current value is 1, this indicates aberrant behavior. Aberrant behavior detection must be enabled for the target, which requires RRDtool 1.1.x. This threshold may be conditioned on the current value of the datasource. In this case, the threshold is only triggered when both the FAILURES RRA is 1 and the current value of the data source is within a specified range. This range is specified via two colon-delimited arguments; the first is the min or "n" to specify no lower bound and the second is the max or "n" to specify no upper bound.

Alarm Actions

After the monitor threshold is checked for the current value, Cricket may execute one of several actions.  Each action requires one or more arguments, which appear as a colon-delimited list following the action tag in the monitor threshold specification.

SNMP: Generating a SNMP trap is the default action if the action tag is omitted from a monitor threshold specification. To support this default and for backwards compatibility, the action SNMP does not use the action arguments field in the monitor threshold specification. The SNMP action instead requires the attribute trap-address to be set for target. The traps Cricket sends are marked with the enterprise OID ".1.3.6.1.4.1.2595.1.1".  The generic type is 6 and specific type is 4 for failure (violation) of the monitor threshold criteria and 5 for success (recall the trap is cleared on the first success after one or more failures).  There are currently nine varbinds: the monitor type,  the monitor threshold string, the target name, data source name, cricket user name (set to "cricket" on Win32 platforms), instance number (to distinguish targets with multiple instances), instance name, contact name (based on the html dictionary entry contact-name), and data value. These varbinds are set (and could be customized) in the sendMonitorTrap() subroutine in Monitor.pm.

MAIL: This action sends email to a specified address via a Berkeley mailx compatible mail program. The first action argument is the program to invoke to send email. It is assumed that this program is compatible with Berkeley mailx. That is, the program accepts piped input as the message body, and supports a "-s" command flag to specify the subject.  If you don't have such a program on your system, you may wish to customize the code in the sendEmail() subroutine in Monitor.pm to utilize your email program. The second action argument is the recipient's email address. Note that as in the example, you may need to escape special characters. Both arguments are required. The mail message body includes the following information: the monitor type, the monitor threshold string, the target name, data source name, the value of the data source retrieved from the RRD file, and the instance number (to distinguish targets with multiple instances). To change the contents of the message, customize the sendEmail() subroutine in Monitor.pm.

FILE: This action appends and deletes entries (lines) from a file. When the monitor threshold criteria first fails, a line containing details in a space-delimited format is appended to the file specified as the action argument (the FILE action has only one argument). Subsequent failures do not add multiple lines to the file. The FILE action essentially ignores persistent-alarms = true (though some overhead is incurred to detect duplicate lines, so persistent-alarms should be set to false when possible for targets using the FILE action). When the monitor threshold passes again after one or more failures, the line is deleted from the file.  The line details include the target name and the data source name. To include other details, customize the LogToFile() subroutine in Monitor.pm.

EXEC: This action executes a shell command or script. The first action argument is the shell command or script to execute when the monitor threshold criteria fails. The second action argument is the shell command or script to execute when the monitor threshold passes again after one or more failures. The EXEC action provides a mechanism by which automated corrective action can be taken.

FUNC: This action is similar to EXEC, except that a perl subroutine defined in the Cricket scope is executed. The first action argument is the function invoked when the monitor threshold criteria fails. The second action argument is the function invoked when the monitor threshold passes again after one or more failures.  To use this action, you must first modify the entry in the func.pm module to set the global variable gMonFuncEnabled. Using this action requires customization (you must write the subroutines). While this mechanism provides complete flexibility in handling special cases, the invoked subroutines cannot easily accept arguments (this can be done, but the argument list must be included by name in the action arguments which can quickly become unwieldy). If your function requires access to arguments available in the Alarm() and Clear() functions, you might consider adding a new action tag (and sharing your work with the Cricket community).

META: This action is meant to be used to shared threshold monitoring event data with other external systems. This action does nothing. In the sense, that no external action is initiated. There is no mail sent, no SNMP trap generated or any other specific action. What it does is let cricket know the fact that an alarm has been generated or cleared. Cricket stores all active alarms in an internal format call meta files. These files are stored in the cricket-data directory along side each target.rrd file that has monitor-thresholds defined for it. The meta files store alarm data for all Action types.The monitor-threshold line itself and other data is stored in the meta file. Arguments are arbitrary.

Using this action requires customization (you must write the external interface script). The most common uses for this are for sharing event data with event management systems such as NetCool, BigBrother and others. Event management systems often support SNMP, proprietary agents or APIs. This permits a flexible way of interacting with these systems with something other than an SNMP trap.

To provide this, your external script must load the config-tree in memory, query it for active alarms and configured monitoring thresholds and send messages in the appropriate format to the event manager. An example script is provided in the util directory of the Cricket distribution, metaQuery.pl. Note that this is not to be mistaken with real time monitoring, as you have to wait for the collector run to be finished before querying the config-tree or else risk missing a new alarm until the next query.

Monitoring Span

Cricket monitoring thresholds can be extended to look for consecutive threshold failures. The SPAN keyword will require a threshold to be crossed an arbitrary number of consecutive times before triggering an alarm. The keyword SPAN and a span-length value are required to enable this action. At the first threshold verification that passes, the alarm will be cleared. Threshold crosses that have not been promoted to alarms are stored in the meta file associated with the monitored target. Using the following meta file format:

<monitor-threshold> <timestamp-of-first-failure> failure lastval <ds-value-at-time-of-first-failure>

When the time stamp of a threshold cross is older than <span-length> * %rrd-poll-interval%, an alarm is generated. This option is fully compatible with the persistent-alarms option.

 

Questions or comments: contact Jake Brutlag

cricket-1.0.5/doc/monitor.gif0100644000114300000000000002126007760540746015052 0ustar bertdwheelGIF89a)0ooC?& foo|?&Yss&O/V3_L& `/]f??sLM|??&L|)Ϧ)0OOvF  cO/Lf߆fMs@_9S33f3OO/@//߲-߆-C#rV_ _LS_F__y/ iߓoCϏiC__9lrIi#i|=VIV߲9߆9/&]oYP Ϧ=O?VoY/& _9?2Y?2 oCP66O?`!^,) ,,  1ʳϲԆ &Ս ܄ٖބ& 0|Cb"J0* L0CIGR${GaK 0C$p/Li#HYgLFVC Jr)TUR"hД% \" hd,چj/RL0NN/*|D:rO( \!-O5 @@h]>߽Xصr0,İA-  .lP,HK R-3r1 0Ba(Ȃp#,nt{Js`B ,y"NtX5=c);ӣON ac$"R>!WF 8?)HA ?7 AWA@@pr]}g Ax6`ngҋYqCJph銱!jo,pHF CTC![7JvJKq`}!!׬*ыjcBב˓ /<;BS qU F`nu;u aw튇q@q5o,D 0 $E6_# 0Ha'm\Qy>had<^\{V+>an@n 0D @oQ=e ԞD fx]C\r5h ]'UP0=TS[ r5+N8ZF !Q&ʂ8,Wޚ,D10,R^ PnR[xˀ6F̓eA[Oo J݂ >H-/!G}6&h\0R$CW`=DbAVaG_䵿x+C<;N<AlUC[\ " Ok@"ֶPtYԼ扷$l!X:)-.ljP"(t)@r c`nz@VK@.#0֧(bD$v1n2Xa0(9yEn16770B!Z+XEDw U;s fI ׮!f #_"]CH@=mH ȀrJv܋: + ^/H8T N+ʍrc{ /ZF3%t&4 A'TY2t%ӝ̀8 $@629Ih")< Af<a9M4HAkxj‡%3@[:؛V>Y * , TT>:J).PRXciZ~ԨʂCP)bBRXC!`yب*a5TVR57rˮdZh xX[z' v1&EDȬ74%mIH;cFSgHv<` h3x%i}b["]:3A;[GaX&-uS`]TiT85MAkTa<!3V6Q@nc~*,M5~Jõ@k.Z\ ΪCx@U 'RQĺZr\r#?D]U*pl]笌K yWoe@!gky)1V[R1Iy2W1p@qiWF -m0[r3!Ϲqjpo(;jzb/PzE0[K TIjUWUj ~cPk^89nۊ#.r ^'7ckX-2.2 IJRx,1YveR1?fZlvIQF:5ګr'a!-ǭ.doFɱ\lCaA94AxjIAџDp)~kf!ܠ,`*`1dMbS|״b1r"ioM;%1ÓlgcCC4ɀtdoI'l!|o7eyӭY{2C5%s99 Axh>SS4 A?I_)9L#gyT*BAXFD>U ޔZe!_zWo8Lz<(z_k1, a_ bm,6ˮ]c>6ԇ̝kgԻZ82Ki#&P ~[~dY?@~-3̥(a#4oxeQ4x1^ ks^p7<_x3P_$_7%u^`iq`FDU b )`&Nʆ/-PYM}uut1߃mMLffIfmIA2aOK-?>v @t_cM?nL&Vm'I|1p$5745#6pBaT8`PSCg88@z p:r{ `lUsF+-G6aQW@-r, rA,UXqAǑ!a-F T+R!)?C`h<9!47Cnl@m)uZ=]~B0e$&5&r=Yv/?t`Gx<'iN#>Z]N|XWw695h%TA^p5 e6^o)ybdB՘r% ]iuSa1B84p| ; *rz"  @2j!!&Z'~J=8P 2#e #U"&O&Xcb%b&UA(b t f*(}R " VA |o iP`zڧ~,У>:Z*xXZz Aک 1:jzꩬڪZ : A?jr Z z: Z. ڬ)z)Z` * P pJZ Zp'QP8K@J.Я ZPUPE کP京 B9 ,p {I` K κ t* Pp~ܚ(P0ƒ Ϛ˻1~ ѼDT> o ؗU08ǡ"|~J-] Up\y^8كNןZU]ƹm2 `NE  m׭ܣ 0 K+뷴. v)Ўξ )pp RRhXXP‚U@ȒXĒ$YpÂP Ezr(:K;;˻xaQ{ER`aL\\A}^mR[ ]p$|laO+N7=@σ>e e3 2(r+H1;̍P1q$\,=] JR gk%C 0Tz$ 8 Kag`ɒ-.Р/De踯׾@$ #@RH臘$.z"XI(%іUX %>Y[$ ^>-$pO![lR"z @I,H0 L؟d J*Ԃ"-(ŢRȰ%ODd>yÔ[G`X 9#HaˎYHG8S?BP9YfcX&SȬJ XhRCXMBBfPu∋2" &U3ώLBJ#c9 ;Ĥ]5[f ,hFdبp Y6ZnjiuvuJXk%*bwAuʚn Ʃn r |@W"'EBy!}BK̂!**Wa!'D=8o{&r[^ bS018P)(;s3[gWJ21=3b#?gPXRY1hvїlP"f䂒JrW'sZsYMR}5TzHѣL}Hjt {f-E"fثZE]p6PfVfMh&҅ 0ب7aZЅj^grutԵEܻ B a,\.V,"*鯅 ,!$n ?0 =5F@]A6.Na֓`cU!w%KK[`^\"@j֍PK6̄>]| )9B0mm(q 1m'rGԢy)H=! %$'QЂ% Pkv͐.\\-'2a&" 0̊³%]ljj8GLF)N1Y(b\7U..HBt( V B C &n:%h WhE`+@ye`C,—aCX% ņ8@ zY.AsXZ=܇:׍`ₘ d.GC?ѰHYD@@IWGB$#Ҧ'9̤pj&1pipjNh NiCm2huMZQ\2'Ҝe65Y*MΠyhE5V_D"X=e܈,tA u3?x11 ^( "P+ΔEw ZU:O!*TCL`)3 l Re]E=G(,V(aP1Q2Vx:Pu0 TtQd5N>Yi 05bOt,-&6%vs+AfxEgqMQ@+l'e(`ZSʪrO^ oCYV,NrQIqubEeL@@\ qƥi@p `Nз"XB pPQ1/S,'Z|M@ E A&` bV06Eix+m:59.pmhD*H. !Xm )&!)$*]IHJB4ƆϱmQ-r(wG #* +1NB9:y$h)8Nْj,I*u 1K#;`[~ZRy BY,]r($.v`y)FNi6x 0--6q'>覢`nT7ٚ >sQHtEq:L5[n1i؄"U\9kw*)yJJ9v!yL("ve,D *Zanth[#Wh )#&m0Qa:f#H[nZ#Z:lOދ7 "v40`Yu|6;@xzdG7frY`WeJV`{"(LbWgeBqZ(V#Ҿt VWҭo9i{X`Jȣ(>(D)\XFʴ3 P"ܡc &ΣrF̵@N) R"Z5h(wA^wZ4!h4h &w-RNCR 7tE9"M15#E ӃΔD %m񂏵EcX{. +dEkȆ{w.҆f2uHW䁅h/fvÈ)oiKtHȉ艟(]m6S`elH?(uH؆Hh3G~X'h"$<(3ahmvP((HWo騎Ĩ$؎ŌHhHA 4ȏLO]]G刐 yQ*G( " hL;cricket-1.0.5/doc/new-devices.html0100644000114300000000000001701610031605175015756 0ustar bertdwheel Setting up New Devices in Cricket

Setting up New Devices in Cricket

Cricket is designed to be flexible enough to be able to monitor more than just router interfaces. The sample config tree that comes with it will help you learn about its capabilities, but to monitor other things, you'll need to write your own branches of the config tree.

Read this document to learn how to get the most from Cricket, by teaching it to gather data from all kinds of sources.

Creating Custom Target Types

Cricket gives you complete control over the contents and layout of your RRD files. This means that if you are going to be doing any serious target-type hacking, you need to understand the following concepts:

  • Round Robin Array (RRA)

    This is where data is stored in an RRD. There is one RRA for each scale of data, for example 5 minute samples, 30 minute samples, and 2 hour samples. The same data is in each RRA, except it is sampled at a different rate. If an RRA can hold 12 samples and it is updated every 5 minutes, then it holds data spanning back one hour. The 13th sample will overwrite the 1st sample. Detailed information about RRD is available from the RRD Tool documentation and from the RRD Tool tutorials.

  • Data Source

    A data source is one line on a graph, or one column of data if you think of the RRD as a tabular listing of data. One RRD can have many datasources in it. All up them must be updated with new data at once. Not all of them need to be graphed at once (see the information about datasource "views" below).

  • Target

    A device that we are keeping stats on is a target. There one RRD file on disk for every target. You can think of each target as a leaf on the config tree.

  • Target Type

    The type of the target determines what kinds of data sources and RRA's makes up the target. When Cricket goes to update a target, it uses the target type to figure out what data to fetch about the target, and how to fetch it.

  • Data Source Source (ds-source)

    This is the rather unfortunate name for the attribute of a data source that tells Cricket precisely where to fetch the data from, and which data to fetch. Encoded in the ds-source is the retrieval method and a description of the data we want.

Now, with all those terms under your belt, you'll understand this statement: Using the datasource, RRA, and targetType dictionaries, you can completely control the kind, source, and quantity of data you store. The RRA's in the sample-config reproduce MRTG 2.x's data format precisely, and that configuration has proven to work well for most needs. If you are going to be using Cricket for the jobs it comes ready to do, you'll simply need to copy the sample-config tree and make minor changes to the targets it specifies.

If you need to customize Cricket to talk to other things, then you'll be following the following general steps:

  1. Create entries in the datasource dictionary that tell Cricket how to fetch your new measurement. If you are using SNMP, you'll probably want to add an entry to the OID dictionary in the same subtree, to make your datasource entry readable. For instance, say we wanted to use Cricket to talk via SNMP to an agent running on a Unix machine, and fetch the load average. We would add the OID for the load average to the OID table, then add a new entry (named "load-average") to the datasource dictionary that described this new datasource.
  2. Create a targetType entry to tell Cricket what datasources make up your new target-type. Assuming the RRA definitions from the --default-- entry are right, you only need to add set the "ds" tag for this entry. For the load average example, we'd put "ds=load-average" into a targetType entry named "unix-machine".
  3. Create a new target who's target-type tag is set to the targetType you created in the last step. If you are using SNMP to talk to the host, you need to make certain the variable snmp-host is set (since it contributes to the snmp variable, which in turn is required in SNMP ds-sources). You could set the snmp-host tag explicitly for every tag, but that would be a pain. Instead, you could choose your target names to be hostnames, then use the auto-target-name to set snmp-host correctly all the time, by adding a line like snmp-host = %auto-target-name% to the --default-- entry for that sub-tree. If you have a rare case in your system where the target name is not the name you want to send SNMP packets to, then you could override the default snmp-host by setting snmp-host directly for that one target.
  4. Run the collector on your new subtree, using the "-loglevel debug" option. This will tell you exactly what Cricket thinks things are set to when it tries to fetch your data, as well as what exactly happened when it went to fetch the data

If you come up with an interesting configuration, please share it with the Cricket community by submitting it to the Cricket contrib site hosted by the nice folks at GNAC.

If you need to add a new kind of data gathering subroutine, you'll want to read the code to snmp.pm very carefully and make a module like it to collect your data. It's not too hard, but it might not be worth the effort -- consider using a simple Perl script to let you fetch the data via the exec method. See the http-performance sub-tree for an example of how to do this.

Cricket version 1.0.5, released 2004-03-28.

Copyright (C) 1998-2000 Jeff Allen. Cricket is released under the GNU General Public License.

cricket-1.0.5/doc/perfmon.html0100644000114300000000000001300007760440275015215 0ustar bertdwheel Perfmon Reference Guide

The Perfmon Reference Guide

Introduction

Perfmon in Cricket is a bit tricky to master but ultimately worth it if you wish to do long-term performance trending on the many useful perfmon counters in NT.

One thing which makes it so tricky to use perfmon counters is that many of them are "raw" counters which require the use of derived, and computed historical data to extract any meaning from them. RRDtool makes it easy to perform the computations. With a bit of research you can easily discover what you need to feed into the Cricket configuration to get useful data.

Assumptions

This document assumes the following. You have a working Cricket installation, you have some knowledge of Perfmon and how it works (run perfmon.exe for a crash course), you're attempting to extract data from a box running the Windows(tm) operating system, you have some knowledge of RPN (Reverse Polish Notation), and finally you feel comfortable to make very light modifications to the Cricket code during the "discovery process".

ds-source Parameters

Here is a breakdown of what the perfmon ds-source requires to operate:

perfmon:host-name:object name:counter name:instance name:special options

  • perfmon tells Cricket that it's to call on the perfmon libraries to perform data collection. This is a required field.
  • host-name tells Cricket what host name to connect to. In most cases you'll want to set this to %auto-target-name%. This is a required field.
  • object name tells Cricket what object, or class to collect sub-classes from. This is something like System, PhysicalDisk, Memory, Network Interface, or Server. Using perfmon.exe can give you an idea of what is available on a particular system. While this field isn't required, you'll want to put something there 99% of the time.
  • counter name tells Cricket what counter, or sub-class to collect counter data from. This is something like % Processor Time, Total Bytes/sec, or Threads. This is required only if something is in object name.
  • instance name tells Cricket what instance to fetch from a counter. Most counters don't have instances, a few do such as PhysicalDisk, and Swap. Most instance counters have a '_Total' counter which is a combined total of all the instances. This is not a required field unless a counter has instances.
  • special options tells Cricket special processing options detailed later in this document. It takes the following arguments: perftime, perftime100ns, perffreq, and noscale. Most cases you won't need to use any of these but some counters require or necessitate them.

Collecting Data Part 1

There are many types of perfmon counters. They for the most part all require different calculations to give cooked data from the raw numbers. To help aid in figuring out what kind of counter is what, there is a program with cricket in utils called perfInfo.pl. For the purpose of this demonstration, we'll collect on the % Processor Time counter.

perfInfo.pl accepts arguments similar to what Cricket uses for data collection. You can run perfInfo.pl -h to get a list of all the switches it needs.

Since we don't know what kind of counter % Processor Time is, we ask perfInfo.pl to tell us.

"C:/Cygwin/bin/bash.exe"-2.03$ perl perfInfo.pl -o processor -c '% processor ti
m-i '_total'
* Setting server to .
* Getting counter type(s) for % processor time
        * Traversing multiple counters for % processor time
                * Found!
                * % processor time->_total is counter: PERF_100NSEC_TIMER_INV

Formulas are from MSDN - subject to change. Author of this program takes no
responsibilities for inaccurate information!

Symbols:
TB   Time base. If TB is not used in the calculation, then it indicates the
     units of the raw data.
B    The base value used for calculating performance data types that require
     base.


Platform SDK

PERF_100NSEC_TIMER_INV

Timer for when the object is not in use.

Element        Value
X              CounterData
Y              100NsTime
Time base      100Ns
Data size      8   bytes
Display suffix %
Calculation    100*(1-(X1-X0)/(Y1-Y0))

=end
        * Traversing multiple counters for % processor time
      
cricket-1.0.5/doc/reference.html0100644000114300000000000027214610031605175015512 0ustar bertdwheel The Cricket Reference Guide

The Cricket Reference Guide

This section is the definitive reference for each and every tag that Cricket knows about. Use this section as a reference when you need to know the exact format for something, or the exact behavior of a certain tag.

File Format

Cricket config files are simple text files arranged into a tree-shaped set of files called a Config Tree. They are processed by a Perl module called ConfigTree.pm. This setup would make it possible to use one config tree for Cricket and other clients of ConfigTree.pm. At this time, there are no other programs that use the config tree design, but there may be in the future. It was set up this way because the config tree will grow to contain an accurate picture of your network; it makes sense to try to leverage that information to other network administration tools. The overriding philosophy here is duplicated information is bad: it is hard to maintain, and it invites silly mistakes.

The Cricket config tree is 100% incompatible with MRTG configuration files. There are not even any plans to write a converter, though one could probably be written. When you choose to use Cricket, it's a very good idea to approach your config from a fresh perspective, to take maximum advantage of Cricket's features.

The comment character for Cricket is "#". If a "#" is found at the beginning of the line (or is preceded only by whitespace) then the "#" and everything after it will be deleted. Completely blank lines are discarded from the input too.

The fundamental unit of a config file is a chunk. A chunk is made up of one line which begins in column one, and one or more lines which have whitespace before them. In this way, you can continue a long line to another line by simply inserting a newline and some whitespace. When it is glued back together internally, the newline and whitespace are replaced by a space. A chunk should have at least one word (a group of characters that are not whitespace) at the beginning. This word (called the "token" by the code) tells ConfigTree.pm what kind of chunk it is dealing with, and what kind of parser to use. If it is an unknown token, ConfigTree.pm can be told to ignore it. By default, unknown chunks cause a warning, but not a fatal error. Clients of ConfigTree.pm can control this; Cricket chooses to ignore unknown chunks.

Text Parser

This parser is used in cases where free-text is useful, for instance in the HTML dictionary. The first two tokens are parsed out, and they serve as the chunk type, and the key. Everything after these first two tokens is considered the value.

Simple Parser

This parser is used for simple mappings, like the OID mapping. An example of a valid OID line is:

oid  ifInOctets  1.3.6.1.2.1.2.2.1.10

The format is a token, another token (called the key; ifInOctets in this case), and finally the value. The value can have whitespace in it, but only if it is surrounded by double quotes ("). Embedded quotes are not yet supported.

Tag/Value Parser

This is the most useful, and most commonly used parser. Here's an example of a Tag/Value chunk:

graph   ifInOctets
    color       =   dark-green
    draw-as     =   AREA
    legend      =   "Average bits in"
    y-axis      =   "bits per second"
    scale       =   8,*
        

Like the others, it parses out two tokens, the chunk type and the key. After this, it parses a series of tag/value pairs separated by whitespace and the equals sign ("="). The values must be surrounded by quotes to embed whitespace, like the Simple Parser. If you repeat a tag, only the second occurrence will be used.

Miscellaneous

All tokens and keys are case-insensitive. The tags in a tag/value chunk are also case insensitive. The values will have their case preserved when they are used by the system.

Tag names can have numbers, letters, the period symbol, the dash, and the underscore. Avoid using other punctuation, since those symbols are used by Cricket internally.

OID tags are more limited, to conform with the SNMP spec. A tag in an OID dictionary can be made up of numbers and letters. Dashes are not allowed.

Target names should consist of letters, numbers, dashes and underscores. Do not put slashes in them, since they end up being used as parts of filenames.

Inside of double quotes, on the right side of an equals sign, anything goes, with the exception of the double quote itself. There is currently no way to include the double quote in a string.

Avoid choosing names that begin and end with two dashes (like --default--). Cricket uses some names like this internally, and future additions will make use of these names.

The following table shows which tokens are parsed with which parsers:

Token Parser
target Tag/Value
datasource Tag/Value
targetType Tag/Value
graph Tag/Value
view Tag/Value
color Simple
oid Simple
html Text
event Tag/Value
rra Simple

For some tags, you can put Perl expressions into the value. (inst-names is one such tag) However, double quotes are often useful in Perl expressions. That's problematic, since at present there is no way to put quotes into values. Instead, you should use single quotes. Thus it might look something like this:

inst-names =   "('Port 1 Ethernet', 'Port 2 Ethernet',
                 'Port 3 FDDI', 'Port 6 FDDI',
                 'Port 7 Ethernet' , 'Port 9 Ethernet')"
                

Dictionary Reference

In this section are detailed lists of every tag you will find in every dictionary.

Jump directly to:

The Target Dictionary

For each target known to Cricket, there is a target dictionary. The target dictionary holds all the information needed by Cricket to collect and store variables from a component. The target dictionary is expanded with respect to itself immediately before it is used to collect or display data.

collect
Default value: not set

If this tag exists, and it is set to false, then this target will be skipped when the collector comes across it. This is useful if you no longer want to collect an obsolete target, but you still want it in the config tree so that you can look at it with the grapher.

copy-to
Default value: not set

If this item is set, it tells Cricket to send the data it collects somewhere else after it is added to the RRD file. The value of this tag is interpreted as a colon separated list of two things. The first is where to send the data. The second item is used on a case-by-case basis.

To send SNMP traps, set copy-to to a string like this: trap:public@traphost.company.com.

The traps Cricket sends are marked with an enterprise number of "1.3.6.1.4.1.2595.1.1" (which is enterprises.webtv.wtvOps.wtvOpsTraps). Their generic type is 6, and their specific type is 3. The first varbind is the full path to the target, including the target name. The other varbinds are the actual data. The OID's for these varbinds can be ignored. They are not significant.

To send SQL updates to a database (see SQL HOWTO), use a string like this:

sql:driver,user,password

driver is a string like: dbi:Sybase:NMSDB.

This is a new extension to copy-to and while it is designed to work with as many platforms as possible, it may not work with all types of databases. It has only been tested against SQLServer.

New copy-to types can only be added by editing the source to the collector. Please send any patches to the Cricket maintainer, so others can use your nifty copy-to method.

directory-desc
Default value: unset

This string is displayed in the description column of the directory selection list, if it is set. If not, the column is simply left blank.

disable-short-desc
Default value: unset

Hold on tight, this is a weird one. If this is set in the dictionary of the first target displayed in the "Targets that are available" table, then the table will have only one column. This is useful when you have lots of views and don't want to lose the space used by the description column.

display-name
Default value: same as target name

You can use this tag to use a more user friendly name for a target for display purposes. If you do not set it, the target name itself will be displayed.

events
Default value: none

The pre-1.0.4 syntax of specifying events as a target tag is deprecated. The events tag is now a view dictionary tag.

hide
Default value: not set

When a target sets this tag to true, the grapher will not list this target in the hierarchical tree view. You might do this when creating multiple-target graphs via targets or mtargets tags, to suppress the standalone components to show only the summarized graph.

inst
Default value: none

This is the SNMP instance number. Cricket only really needs this value to be set correctly if %inst% shows up in the ds-source tag of the datasources you are collecting. If inst is set, Cricket will attempt to evaluate it as a Perl expression.

Note: there's no protection against side-effects from this expression, so to avoid a security problem from an inst like unlink('/etc/motd'); you should make certain your config tree is only writable by you.

The result of evaluating the instance is examined to see whether it is a mapped instance, a scalar, or a vector. If it starts with "map(", it is a mapped instance. The instance mapping algorithm is used to derive an instance number from the rest of the data in the target (see Instance Mapping for more information on this), then the target is processed as though the instance number had been a scalar all along.

If it is a scalar (i.e. inst=1) then it is processed normally. If the instance is a vector (i.e. inst="(0..12)", or inst="(1, 3, 5)") then the target is processed once for each value in the list. The target dictionary is expanded with respect to itself for each instance, so it's possible to embed %inst% into the rrd-datafile tag. In fact, it's critical to add %inst% to some part of the rrd-datafile tag, or else the data will all be stored on top of one another in the same datafile.

Note: To protect the instance against interpretation, use the perl quote operator: inst = qw(%InstanceName%). This is required if instances look like floating point numbers to Perl, for example if you have an instance like 4.10.

inst-names
Default value: none

When making the instance selector table, Cricket will use names from this tag instead of the instances themselves. If it will be needed, inst-names is eval'ed (just like inst). For each instance in the inst tag, Cricket looks for a corresponding entry in the inst-name vector. If it finds one, it uses that as the instance name, otherwise it defaults to the instance it fetched from the inst tag.

long-desc
Default-value: unset

This description shows up at the top of a page of graphs, in the "Summary" section. There is more room on this page for a longer description. You can include your own HTML as long as you close all tags that you open. If you don't feel like setting this for every target, set it to %short-desc% in the "--default--" target, and the short description will show up on each graph page for every target which does not have an explicit long description. (Such is the power of the config tree.)

monitor-thresholds
Default-value: none

Monitor thresholds are discussed in detail in this document.

mtargets
Default-value: none

If this tag exists, then this target is considered by Cricket to be a multi-target. To the collector, this means that it simply doesn't need to collect anything, and can skip the target. To the grapher, it means this target will aggregate the data from several different targets all onto one set of graphs. It will plot the data from each of the different targets all on the same graph. It assumes that the target-type of each of the specified targets is the same. It works best if the graph dictionary for each datasource does not specify a color (otherwise you'll end up with more than one line with the same color). It enforces only one AREA graph--it will convert the others into LINE2 in order to ensure they can be seen. It does not currently support vector instances. Other than that, the syntax matches that for "targets". This tag can also be used in connection with the mtargets-ops flag to perform arithmetic operations.

Note: This tag will not work with vector instances at this time. If you absolutely need it to work, you'll need to use multiple separate scalar targets to poll the device's ports, and then use the mtargets tag to graph each of the scalar targets.

mtargets-ops
Default-value: none

This tag is used in connection with the "mtargets" tag. This tag lets you perform arithmetic operations on multiple targets. For example, this lets you add multiple targets together and graph the results. This tag must either be set to an RPN notation, or to a short-hand form, like "sum()".

If set to an RPN expression, it should be defined as a comma separated list of operations, such as "+","-","*","/". This is used as a RPN calculator, operating on the targets listed in the mtargets definition. So, if you list three targets in your mtargets definition, you could define mtargets-ops="+,+", and the result would be a graph of the three targets added together.

If this tag is set to "sum()", the correct number of pluses will be substituted to make a valid RPN expression to sum each of the targets in the mtargets tag.

If this tag is set to "avg()", it will be replaced by the correct RPN expression to calculate the average of the targets in the mtargets tag.

The HTML summary section also has the same set of operations done to the values there. The operations are done to each datasource defined for that target-type. If you have defined a scale for the individual datasource, these operations are done on the scaled values.

order
Default value: 0

The order tag is used to control the order that targets are displayed. It has nothing to do with the order that they are polled. (Though it might work like that someday.) When the display is laid out by the CGI application, it sorts the targets from the highest order to the lowest order. Targets which have the same order are sorted alpha-numerically within their order.

persistent-alarms
Default value: false

This tag tells the Monitor agent that if it receives an alarm to send traps every time it detects an alarm condition. The default behavior is to only send an Alarm/Clear trap. This can be useful for sending many SNMP traps into an application which can tally the amount of failures, or to have it execute a program multiple times. Setting this tag to 'true' turns persistent alarming on.

rrd-datafile
Default value: none

This tag tells Cricket where to put the data it collects, and where to find the data it will graph. Since there are no guarantees about where Cricket will be running from, you should use a full path here. The sample-config ships with a one-size-fits all definition for rrd-datafile in the root Defaults file. Except for special cases, you can probably use it and it will generate the filenames for you automatically.

rrd-poll-interval
Default value: none

This tag is used to set the step size (in seconds) for the RRD file when it is first created. This value cannot be changed once the RRD file is created, so if you need to change it in the config tree, then you must remove the RRD file, forcing Cricket to re-create it, to make the change stick. You will, of course, lose all the data you have collected so far. It is critical that the collector runs on each target at least as often as the rrd-polling-interval, otherwise RRD may mark the intervening polling periods as unknown.

RRA's are defined in terms of the fundamental polling-interval of the RRD. Thus, if you chose to adjust the rrd-polling-interval (and then run the collector at a non-standard rate) then you should re-visit the RRA definitions and probably make adjustments to them. A particular example of adjusting rrd-polling-interval would be using Cricket to monitor a 155 Mbit OC3 link. Because an SNMP Counter32 can wrap in under 5 minutes at bandwidths above 100 Mbits, it's critical to fetch the data more often, or else RRD will not be able to correctly detect and process the counter wrap.

Hopefully in the next few versions, we will have an example of a part of the config tree optimized for very fast interfaces.

rrd-max
Default value: unset

If this tag is present, it will override the same tag in the datasource dictionary. See the entry on rrd-max there for more information.

rrd-min
Default value: unset

If this tag is present, it will override the same tag in the datasource dictionary. See the entry on rrd-min there for more information.

short-desc
Default value: unset

This description shows up in the target selection table which the CGI program displays. It is a good idea to keep this description to one line. You can include your own HTML in it as long as you close all tags that you open.

show-path
Default value: false

When set to true, the current path will be displayed in the header for the affected target; with hyperlinked path components for easier navigation in the tree.

snmp
Default value: none

This tag is not directly used by Cricket except when it is doing instance mapping. However, it is convention (as exemplified by the sample-config tree) that this tag should hold the SNMP community string, the hostname, and the port number in the exact form that is required by the snmp:// ds-source specification. Thus, ds-sources can simply start with snmp://%snmp%/, and they will incorporate the current host, port, and community string into the ds-source tag as soon as they are expanded with respect to the current target.

When using instance mapping, the snmp tag is used directly, so it must be present, even if you are using another convention to get the data into the ds-source tags.

snmp-uptime
Default value: none

If an snmp-uptime tag is specified, collector will retrieve the specified SNMP object and interpret it as the remote device uptime. If the uptime is less than the rrd-poll-interval, Cricket will assume the device has been rebooted and will ignore the previous value of any COUNTERs on the device (effectively instructing RRD to just store the value as a baseline for the next sample). This tag should only be used if you must use COUNTER rather than DERIVE as the datatype for your data, because checking the uptime will not protect you against a host of other causes for spurious counter wraps. Use at your own risk, most users should just use the DERIVE datatype for counters.

Example: snmp-uptime = 1.3.6.1.2.1.1.3.0

This example shows how to use the system.sysUptime object to determine the device uptime. This tag only makes sense in the target --default-- stanza, because specifying it will affect all collected counters.

If not specified, Cricket will not monitor device uptime for reboots.

summary-loc
Default value: top

This tag controls where the "Summary" box appears on the page. There may be instances where you want it on the bottom instead of the top. This could be useful for aggregate pages where you only care about the graphs and not so much the content. To have it display on the bottom, change the tag to 'bottom'.

targets
Default value: none

If this tag exists, then this target is considered by Cricket to be a multi-target. To the collector, this means that it simply doesn't need to collect anything, and can skip the target. To the grapher, it means this target will aggregate the data from several different targets all onto one report. The syntax is a semi-colon delimited list of targets. The target definition can either by fully-qualified, which would look like "/router-interfaces/Datacenter1/ds3-1", or unqualified, in which case they are assumed to be in the current directory.

For targets with vector instance definitions, it may also be useful to specify which instance you wish to display. You can do that by putting a colon after the target definition. So, it would look something like this: targets="/switch-ports/switch-1:3;/switch-ports/ switch-2:5..7". This would put up instance #3 from switch-1, and instance #5, #6, and #7 from switch-2. Variable expansion is handled in the instance definition section as well.

This functionality assumes that the target-type of each of the specified targets is the same. You also must set a target-type for the multi-target itself. There are some variables which control the presentation of this data, which are also listed in this reference.

targets-long-desc
Default value: none

This tag sets the text which is displayed above each graph in a "targets" multi-target. It should be an array of the same length as the "targets" array. This value overrides the default presentation, and also overrides the tag targets-short-desc. Syntax would look like: targets-long-desc="('Target1','Target2')".

targets-short-desc
Default value: none

By default above each graph in a targets multi-target page, the short-desc of each target being displayed is printed above that graph (as well as some other text). This replaces that text with this. It should be an array which matches the "targets" array. The syntax would look the same as for targets-long-desc.

target-type
Default value: none

This tag sets the target type for this target. It must be one of the target type names from the targetType dictionary. Cricket uses the targetType to decide which variables to fetch, and how much data to store in the RRD.

trap-address
Default value: unset

This tag is used to tell Monitor.pm where to send SNMP traps when it needs to send one. The format should be community@hostname:port. You can safely leave off community and port. The defaults are "public", and "162", respectively.

unknown-is-zero
Default value: unset

When this tag is set and the target is part of a multitarget which has mtargets-ops set, then data which is unknown will be treated as a 0, unless all targets of the multitarget are unknown (in which case the value is still unknown). This is very useful when trying to create aggregate graphs that sum a set of targets that span different time ranges. By default, places where one target is unknown and the others are known will show on the graph as unknown (blank). In some cases, this can result in no graph appearing at all (when the set of known datapoints are completely disjoint). Set this flag to "true" to avoid this problem.

use-gprint
Default value: false

When this tag is set, the summary information for the target will be embedded in the graph below the legend.

In addition, the following tags are set automatically by Cricket for your use in other tags in the target dictionary:

auto-target-name
Default value: automatically set

The short name of the current target. This does not include the entire path from the config tree root to the target.

auto-target-path
Default value: automatically set

The path leading from the base of the config tree to the current target.

auto-base
Default value: automatically set

The config tree root directory. This is especially useful for creating paths which you want to be relative to the config tree root. For instance, setting rrd-datafile to %auto-base%/../cricket-data/foo.rrd would put the datafile in a directory named cricket-data, next to the base of the config tree.

auto-view
Default value: automatically set

The name of the current view, as selected using the user interface. This is sometimes useful to incorporate into strings used in the user interface via the HTML dictionary.

The Datasource Dictionary

This dictionary tells Cricket everything it needs to know about individual datasources. A datasource is a single variable about a target which is polled and logged. For instance, a datasource for a router interface would be "inbound bandwidth". A separate datasource definition is needed for each of the other variables that you might want to measure ("outbound bandwidth", for instance).

desc
Default value: unset

If this tag is set, the value is incorporated into a key at the bottom of the page of graphs. This is a very good place to put notes about how the value is measured, what uncertainty there is in the measurement, etc. You can put arbitrary HTML into it, as long as you close any tags that you open. If there are no datasources with a description, then no key will be printed. Only datasources with descriptions will be present in the key.

ds-source
Default value: none

This tag is used to tell Cricket where to fetch the data from. There is a scheme identifier from the beginning until the first colon. Everything after the first colon is defined on a per-scheme basis. The scheme identifier is case-insensitive.

The per-scheme part is expanded with respect to the target dictionary immediately before it is resolved into measurements, making it possible to plug values from the target dictionary into the ds-source at runtime. Likely candidates for this substitution include the SNMP hostname and community string, the instance number (which is appended to table-base OID's), and some or all of the command line for EXEC datasources.

Here's a detailed description of each scheme.

SNMP

These ds-sources look like this: snmp://community@hostname:port/oid. The hostname part can be either an IP address or a domain name. It will be resolved with gethostbyname() in the latter case. The oid part can include a text label which will be expanded from the OID dictionary.

EXEC

This ds-source is used to tell Cricket to run a shell command in order to fetch the value or values required for a target. After the exec: scheme identifier comes an integer followed by a colon, followed by a command (including whatever arguments are required for the command). The number chooses a line of output from the command (starting from line 0). For example, say the "vmstat" program on your system prints this when given the "-s" argument:

      350 swap ins
     2982 swap outs
      239 pages swapped in
     3298 pages swapped out
          (etc...)
                

Then the following datasource would fetch the number of pages swapped out:

exec:3:vmstat -s

Note that we allow the text output from the command (i.e. "pages swapped out") to go in to Cricket. As of version 0.64, Cricket ignores everything after the first space or tab. It will attempt to interpret the first sequence of non-space characters on the line as a floating point number. Since an integer is a degenerate form of a floating point number, they are acceptable. This behavior holds for all ds-sources, actually, but it is most useful for the EXEC type.

If the initial integer is omitted (and there are no colons in the command line to confuse Cricket) then the line number will be assumed to be 0, and the first line of output will be used as the measurement.

FILE

This ds-source tells Cricket to read a file and find the results on a certain line of that file. The line is specified using the same syntax as EXEC. For example, say the file /tmp/data has two lines in it, like this:

100
235
                

Then this datasource would always fetch "235" from the file:

file:1:/tmp/data

Presumably, there would be some other process updating the data file with interesting data from someplace, or else the graph would be a flat line at 235.

FIELD

This datasource is based on the FILE library. It is used to extract specific fields from a file. It is analog to a cricket version of cut(1).

FIELD ds-source look like: field:filename:key:valuef:keyf:delim

key is the key value to match. valuef is which number of the field that should be returned (defaults to 2). keyf is the number of the field that should be searched for the key. (defaults to 1). delim is the field delimiter (defaults to ':'). For example, if the file /tmp/box contains the following lines with fields seperated by spaces:

thing_1 red 111
thing_2 blue 222
                

then this datasource will fetch "222" from the file:

field:/tmp/box:thing_2:3:1:" "

There is a single space inside the quotes above. As with FILE above, it's assumed that some other process is updating the data in the file. Please note that field numbers start at 1, not 0.

FUNC

This ds-source uses a Perl function to fetch the result. It is not enabled by default, since it requires extra code to be useful, and could be a security vulnerability, if the config tree were not properly protected. To enable it, uncomment the line use func; in collector.

This ds-source uses Perl's eval keyword to evaluate some Perl code at runtime. Usually, this will be a function call to a function you have written yourself and added into collector. A good way to add the code portably is to keep it in its own file and incorporate it with the require keyword. You will need to be careful to avoid stomping on any data that collector uses, since it will be running your code in its namespace. This should be fixed someday, if many extensions to Cricket use this interface.

PERFMON (warning: still in development)

This ds-source is used to extract perflib information from Win32 hosts which don't use SNMP (or where counters aren't available through SNMP).

Setting up perflib counters in Cricket is not a trivial process, and requires the following: RRDtool 1.1.x, comfort with RPN, and ActiveState perl. This module will not under any circumstances work with any non-Windows OS.

Perflib counter procedures are documented more in depth in the Perfmon reference document.

The syntax for a Perfmon datasource is:

perfmon:hostname:counter group:counter name:instance:special

Instance is optional, and only required for instance counters. Unlike the perfmon.exe application in NT. Special is optional as well and is documented in the perfmon reference document.

If you're collecting an "instant" perfmon counter, collection is much like getting something from SNMP. Here is an example.

perfmon:%auto-host-name%:System:Processes

Many perfmon counters return averages which need to be computed against a timebase. This makes collection challenging because you need to first collect several DERIVE-based datasources, and then tie them together with a COMPUTE datasource. The sample-config directory has a large amount of examples with common counters to help, and there is the perfmon reference document.

Please note that this piece of Cricket is still not complete and hasn't been tested very well. Please let the developers know if you find any major bugs or have any enhancement suggestions.

SQL (warning: still in development)

This ds-source is used to for extracting counters from a database server. It's still in development and not very robust.

Here is the syntax:

sql:login:password:query:col:DRIVER

col is used to get a particular column of data returned from the first returned row. Currently any other rows returned get discarded.

DRIVER can have colons in it. Cricket's parser will not treat it like separate arguments to the ds itself.

Here is an example:

sql:anonymous::select top 1 samplevalue from samplednumericdata where idsamplednumericdatasource = 'C04E3055-B282-4EE9-9F5D-CB46DC45C988' order by timesampled desc:1:dbi:Sybase:NMSDB

WBEM (warning: still in development)

This ds-source is used to extract WBEM information from Win32 hosts. Currently this requires ActiveState Perl and will not run under any non-Windows OS. It has not been tested for compatibility against a UNIX WBEM data provider.

The syntax for a WBEM ds-source syntax:

wbem:%auto-target-name%:wbem-namespace:wbem-object:counter:instance

Here is an example to get the same counter as noted in the PERFMON provider example.

wbem:%auto-target-name%:root\\cimv2:Win32_PerfFormattedData_PerfOS_System:Processes

The WBEM provider does not support authentication, so security settings may not allow this to work properly.

More information can be found in the Installing Cricket on Win2k to Monitor WMI Counters document.

Datasources are batched together and executed with as few network transactions or shell executions are possible. For instance, if you are fetching six datasources from the same hostname, all six datasources will be rolled into one SNMP request. Likewise, if you are fetching 3 different lines from the results of the same command, that command will be run only once.

Schemes are expected to return their data as an array of numbers. If the values are of the form "XXX@YYY", then the measurement XXX will be recorded as being taken at time YYY. The time must be in the form of a Unix time_t, i.e. number of seconds since 1970. Currently, this feature is only supported by the EXEC and FILE schemes. RRD imposes the restriction that data can only be added in strictly ascending time order. Each datasource which has a time in it should have a matching time. If this is not the case, Cricket will log a warning, but use the first time it found in the array.

You might use this feature if you are fetching the data from another data collection system which is operating on a different polling cycle than Cricket. You could use a shell script to fetch the data from the other system, along with the time the data was sampled. Then you could format the result into the "XXX@YYY" format, and print it to standard out for Cricket to read.

New schemes can be added to Cricket with minimal effort. Use the file lib/exec.pm as your guide, then add a use statement for your new .pm file to collector.

rrd-ds-type
Default value: GAUGE

The DS type is used by RRD to know how to interpret the numbers fetched from this datasource. There are three possibilities:

GAUGE

The measurement will be directly copied into the RRD datafile without any extra processing. Examples of this type of measurement include readings from thermometers, percent disk space free, etc.

The distributed Defaults file sets rrd-min to zero. If your GAUGE target can support negative values, set rrd-min to "undef", or to the actual minimum value.

COUNTER

The measurements from COUNTER datasources will be treated like SNMP counters. An SNMP counter increases monotonically until a fixed wrap-around point (usually either 2^31-2 or 2^63-2, depending on the size of the data type). To convert a COUNTER measurement into a rate (for instance, "count of octets" to "octets per second") RRD subtracts the previous value from the current one, and adjusts for any wraparound conditions. Any SNMP variable which is marked with the SNMP "COUNTER" data type would normally have its rrd-ds-type set to COUNTER, but unless your datasource guarantees a monotonous increase, it is better to use DERIVE with an rrd-min of zero.

DERIVE

DERIVE is like COUNTER, but there is no overflow check, so negative rates are possible. This datasource type would be useful when you have a count of something (which may increase and decrease), and you want to graph the rate of change.

With an rrd-min of zero, the DERIVE datasource type acts like COUNTER, except that negative samples are treated as unknown. Handling SNMP COUNTERs in this fashion helps reduce the occurrence of spikes in the graphs. Negative values can result from restarting the device or implementation errors in its SNMP agent, and are common enough to really recommend using DERIVE rather than COUNTER.

ABSOLUTE

ABSOLUTE is for COUNTERs which get reset upon reading. Some SNMP variables have this property. Using ABSOLUTE instead of COUNTER tells RRD to compute the rate assuming the value is an absolute count of octets (or whatever) during the last polling period.

The datasource type names are case-insensitive. They are passed directly through to RRD when the RRD file is created. If the datasource type is changed later, you must use rrd-tune to apply the change to the underlying RRD file. For more information about them, consult the RRD documentation for the create command.

rrd-heartbeat
Default value: 1800 (value is in seconds)

The heartbeat for a datasource is the number of seconds that can pass between updates before the datasource is marked unknown. If this tag is changed after the RRD file has been created, then the rrd-tune tool must be used to apply the changes to the existing RRD's.

rrd-max
Default value: U

This tag is used to set the RRD maximum value parameter. If RRD gets a value higher than this number, it will throw the data away, instead of adding it to the RRA. This can be used to filter bad data, though it could be argued it would be better to simply avoid fetching bad data in the first place. When it is set to "U" (for unknown), RRD does not attempt to check incoming data against an upper bound.

If the tag rrd-max is present in the target dictionary, it will override the value found in the datasource dictionary. This makes is possible to assign different maxima to different targets.

If this value is changed after the underlying RRD file has been created, the rrd-tune script must be used to apply the changes.

rrd-min
Default value: U

Like rrd-max, this tag is passed through to RRD unchanged. It can be used to apply an upper bound to data submitted to RRD.

Also like rrd-max, if rrd-min appears in the target dictionary, it will override the rrd-min in the datasource dictionary.

The TargetType Dictionary

The TargetType dictionary tells Cricket which datasources to gather together for a certain kind of target. Unlike other dictionaries, keys in this one can occur multiple times.

ds
Default value: none

Each ds tag tells Cricket about a variable it will be fetching. The value of the tag must be comma-separated list of known datasource names.

view
Default value: none

Each view specifies a comma-delimited list of views associated with this target type. These views must be defined in separate view dictionary entries on the config tree inheritance path of targets of this target type. This implies the view entries do not have to be defined in the same Defaults file in which the TargetType dictionary referencing them appears. However, for a cleaner, easier to read config tree, define view entries in the same Defaults file as the TargetType dictionary that references them.

The pre-1.0.4 syntax of defining views with the TargetType view tag is deprecated.

The Graph Dictionary

The graph dictionary controls how individual datasources are displayed when it comes time to graph them. It also controls display attributes of statistical summary for this data source on the HTML page which embeds the graph image(s). Each named graph dictionary entry (not --default--) should share the name of a datasource dictionary entry. In this respect, the graph dictionary could be merged with the datasource dictionary. However, the separation permits separate administration of datasource definition/collection and display properties in graphs.

Cricket variables in graph tags (i.e. %myvar%) are expanded with respect to a target. For example, in the graph dictionary entry for a datasource, you could specify y-max = %specific-y-max%, then set the tag specific-y-max on a per-target basis. The association between targets and graph dictionary entries is controlled by the TargetType dictionary for the apporpriate target type (because the graph dictionary names match datasource dictionary names).

Graph --default-- dictionary entries can be used to define default datasource graph attributes (such as default line thickness via the draw-as tag).

If your config tree does not utilize views, you should use graph --default-- dictionary entries to define non-datasource specific graphing attributes. These tags include: default-ranges, height, height-hint, interlaced, graph-format, vrule-color, width, and width-hint.

If your config tree utilizes views, these attributes should instead be defined in view dictionary entries and these tags are defined in the view dictionary section. For a config tree that utilizes views, defining non-datasource specific graphing tags in graph --default-- entries is deprecated.

bytes
Default value: none

If this is set to true, then the conversion to SI units will use powers of 2 instead of powers of 10. For instance, if a router is reporting that it's moving 1048576 bits per second through a particular interface, and "bytes" is set to 1 (or "true") then the value will be reported in the HTML summary as 1 Mbit/sec. Without the bytes tag, the value would be reported at 1.04 Mbit/sec, which is slightly misleading. When measuring very large numbers of bytes, the difference can be significant (4.9% in the case of Mega sized measurements, 7.3% in the case of Giga sized measurements). This is the major cause of discrepancies between Cricket and some commercial tools, so make sure it's set appropriately.

The numbers on the vertical axis of a graph are scaled by powers of 2 if the bytes tag is set to true for any of the datasources on the graph.

color
Default value: none

Use the color tag to explicitly control the color of the datasource as it appears both on the graph, and in the HTML summary. If this is left unset, then Cricket will automatically choose colors according to the --order-- key in the color dictionary. If you choose to set it, either set it to one of the color names in the color dictionary, or set it to an HTML-style color specification without the pound sign. For instance, to make a datasource's line red, set color to "ff0000".

draw-as
Default value: ?

This tag tells Cricket how to draw the datasource on the graph. The choices are as follows:

AREA
Draws a filled-in shape representing the value of the datasource over time.
STACK
Like LINE or AREA, but the data values are stacked. Note that the first graph must be drawn as an AREA or a LINE and determines how the STACKed values are drawn.
LINE1
Uses a thin line.
LINE2
Uses a medium line.
LINE3
Uses a heavy line.
draw-max-as
Default value: LINE2

Same as draw-as but for maximums enabled with show-max.

legend
Default value: datasource name

Using legend allows you to customize the datasource name which is displayed on the graph's legend, and in the HTML summary. If you don't use a legend tag, the name of the datasource (as specified by the ds tag in the targetType dictionary) will be used.

max-color
Default value: none

Same as color but for maximums enabled with show-max.

precision
Default value: 2

You can adjust the number of decimal points in the HTML summary values using the precision tag. The default is two. If you simply want the value displayed to be rounded to the nearest integer, set precision to zero. Alternatively, you can set it to the string "integer".

scale
Default value: none

If scale is set, then the datasource is run through a computation (usually a simple scaling operation) before it it plotted or listed in the HTML summary. The tag is interpreted as a string of operations that will be executed by a Reverse Polish Notation (RPN) calculator, separated by commas. When the expression begins executing, the current value of the datasource is already on the stack. Thus setting scale to "8,*" will multiply the datasource by 8 before plotting it.

The RPN operations available are "+" (plus), "-" (minus), "*" (multiply), "/" (divide), and "LOG" (take the natural logarithm of the top of the stack). Any other token is pushed onto the stack as a number. The top of the stack at the end of the string of operations is taken as the scaled value.

This feature is very useful when plotting bits per second from a router interface. Routers tend to report bandwidth in bytes per second, but humans tend to think about link capacity in bits per second. Using a scale tag of "8,*" can bridge this gap by scaling the bytes per second from the router into bits per second for human use.

show-avg-max
Default value: true

This controls the amount of summary data shown for this datasource at the top of the graph page. If set to true, Cricket makes an extra call to RRD Tool to fetch the average and the maximum for the current day of data. If set to false, only the current reading is shown.

show-max
Default value: false

This controls whether or not the maximums stored for a data source are drawn on the graph.

Data collected by Cricket is consolidated in RRD databases in several ways, so as time passes, you lose high-resolution data points, as they are replaced by summary data. The most common consolidation function is AVERAGE, which shows you the average of the data points which were consolidated. But there are also MIN and MAX, for example, and this data is calculated and stored by default. show-max turns on display of the MAX consolidated data.

This is useful for seeing what the peak levels were at some point in the past, since this information is lost by AVERAGE.

si-units
Default value: true

Setting this tag to "false" will prevent Cricket from transforming the value printed in the HTML summary into SI units (i.e. 2000 bytes/sec is transformed into 2 kbytes/sec). The scale on the graph, however, will still have the transformation done to it. Set this tag to "false" for items that are not typically counted with SI units, like system load average.

Here are the SI units that are used by Cricket:

Symbol Magnitude Name
a 10e-18 Ato
f 10e-15 Femto
p 10e-12 Pico
n 10e-9 Nano
µ 10e-6 Micro
milli 10e-3 Milli
k 10e3 Kilo
M 10e6 Mega
G 10e9 Giga
T 10e12 Terra
P 10e15 Peta
E 10e18 Exa

Note that on graphs, 'milli' is denoted by 'm'. In the HTML summary, where there is more room, Cricket uses the less ambiguous 'milli'.

space
Default value: one space

This tag can be used by the extremely fastidious user to control the HTML summary. Usually, one space is exactly what you'd want between the value and the unit specifier of the value. However, for certain units, like the degree sign, no space is called for. In this case, set space to "", the empty string.

units
Default value: the value of the y-axis tag

In the HTML summary, the units tag is appended after the value of the datasource. If a datasource is collecting bandwidth information, for instance, it would make sense to set y-axis to "bits per second", since there is plenty of room for it. In the legend, where room is more limited, it would be better to set units to "bps", instead of letting it default to "bits per second".

y-max
Default value: unset

Use this tag (and its counterpart, y-min) to explicitly set the default vertical scale for this datasource when it is graphed. Often multiple datasources are graphed together, so this tag is better set in a view entry. Refer to the the view dictionary section. Presence of this tag disables auto-scaling, so you should set y-max and y-min together if you use this functionality.

y-min
Default value: unset

Refer to y-max.

The View Dictionary

A view dictionary entry is a container for display elements. In particular, a view is a group of datasources to be displayed together with a particular set of display attributes/options. The same datasource can appear in multiple views. The datasource specific graph attributes are defined in graph dictionary entries, but other features of the graph and the HTML page in which the graph is embedded are configured in the view entry and thus can vary from view to view. As you navigate through the Cricket Grapher the choice of graph pages presented for a given target are the views defined for that target (via the view list in the TargetType dictionary).

The most important view tag is elements, as this tag specifies the list of datasources (with their associated graph dictionary entries) to be included in the view. The elements tag is the replacement for the space-delimited datasource list in the TargetType dictionary view tag in older (pre-1.0.4) versions of Cricket.

Views are not required. If a target type does not include any views, the Grapher will present the user with a list of datasources. But views unlock the full flexibility of the Cricket Grapher.

Cricket variables in view tags (i.e. %myvar%) are expanded with respect to a target. The association between targets and view dictionary entries is controlled by the TargetType dictionary for the apporpriate target type.

It is not uncommon for view names to contain space characters. Such names are permitted (and parseable) in the config tree. Simply enclose the name in double quotes:

view   "Traffic Flow"
elements = "ifInOctects, ifOutOctets"
        

Here is the list of view dictionary tags:

confidence-band-scale
Default value: 2

This tag is ignored unless the holtwinters is present and set to to true. In that case, it controls the multiple of the deviations (RRA CF DEVPREDICT) used to display confidence bands on the graph.

default-ranges
Default value: "d:w"

The default set of temporal scales, colon-separated, for which graphs are initially displayed. For example, specifying default-ranges="d" for a view means only the daily graph will be generated, instead of both the daily and weekly graphs. Of course, the Cricket grapher permits the user to select another temporal scale once the default is rendered.

elements
Default value: none

The elements tag specifies a list of datasources (with their associated graph dictionary entries) to be included in the view. These datasources are presumed to be listed in the ds tag of any TargetType dictionary to which the view belongs. The elements tag may either be comma-delimited (the preference) or space-delimited (to match pre-1.0.4 syntax).

Selecting a view in the Grapher without the elements tag defined in the Config Tree is one cause of the "No graph to make?" error in your web server log.

events
Default value: none

This tag is interpreted by the grapher as a comma-separated list of events to fetch from the events dictionary, and add to the graph in the form of vertical lines.

graph-format
Default value: png (for 4.0+ browsers), gif otherwise

Set this tag to 'gif' to get RRD Tool to create GIFs. The GIFs created by RRD are big and slow, because they do not use patented compression techniques. Cricket will use GIFs if the browser will not support PNG's. This tag is likely to appear in view --default-- entry.

Note that RRD Tool 1.1.x does not support GIFs. It does support other image formats that may be incorporated into a future Cricket release.

height
Default value: 200

This tag defines the requested height of the data area of the graph. This value is passed into RRD, which then draws the actual image somewhat bigger, in order to leave room for the margin, the scales, and the legend. This tag is likely to appear in a view --default-- entry.

height-hint
Default value: none

HTML defines image sizing tags that allow the browser to layout the page more efficiently. Using the Cricket's size hints, you can control what image sizing tags Cricket generates in its HTML output. Most browsers will attempt to scale the image if the size hints passed in via HTML do not match the actual size of the image. The result is a hard-to-read graph that took too long to draw. Thus it is critical that if you choose to use sizing hints to help the browser layout the page, you make them precisely the same as the size of the generated image.

Because RRD pads the image with extra space, it's not possible for Cricket to guess the finished size of the image using the width and height tags. Instead, you need to find this number yourself by measuring an actual image produced by RRD. In Netscape Navigator, you can do this by right clicking on the image, then choosing Open this Image. Once the image is loaded, check the title bar for the actual size of the image.

Like height, this tag is likely to appear in a view --default-- entry.

holtwinters
Default value: false

If present and set to true, the holtwinters tag tells the Grapher to display aberrant behavior detection options on the graph and HTML summary page. These options include display of aberrant behavior recorded in the FAILURES RRA and predicted confidence bands for the observed time series (based on Holt-Winters Forecasting, hence the tag name). The underlying target must have RRD Tool aberrant behavior detection enabled (requires RRD Tool 1.1.x).

If this tag is enabled, the elements tag must contain only a single datasource; otherwise, an error will be generated. Furthermore, the default-ranges tag is ignored for a view with holtwinters=true.

Note that it is not required to define views with holtwinters=true simply because RRD Tool aberrant behavior detection is enabled.

interlaced
Default value: false

Set this tag to true to enable interlaced images for this view. An interlaced image will appear to "fade in" in most web browsers.

paint-nan
Default value: false

Setting paint-nan to true for a view will shade the background of the graph with a pinkish hue for any time period in which one of the datasources included in the view has missing (NaN) data. This option should not be used for views containing datasources with the graph key draw-as=AREA or STACK.

rrd-graph-args
Default value: none

This tag can be used to pass through arguments to the RRD Tool graph command. For example, to make the canvas that the graph is drawn on appear black, set rrd-graph-args to "-c CANVAS#000000". This tag is split on whitespace before it is sent to RRD Tool, so it is not possible at this time to send arguments with embedded whitespace to RRD Tool.

As a second example (running RRD Tool 1.1.x), to change the default font for in-graph text to 12 pt Times, set rrd-graph-args to "--font DEFAULT:12:times.ttf". This example assume times.ttf is available on the path in the environment in which the Cricket grapher is running.

vrule-color
Default value: none

Vertical rules are lines added to the graph by Cricket to help you visually find patterns in the data related to time of day. A vertical rule is placed at each "zero time" which appears on a graph. For an daily graph, a zero time is midnight. For a weekly graph, a zero time is the beginning of the most recent Monday. For a monthly graph, a zero time is the first day of the month. For a yearly graph, a zero time is the first day of the year. At this time, the definition of zero time for a given scale is hardcoded into Cricket's grapher.cgi script.

To enable vertical rules, use the vrule-color tag to choose a color for them defined in the color dictionary. That's all there is to it. If vrule-color is defined, Cricket will automatically add the vertical rules for you.

width
Default value: 500

Similar to height, this is the width of the data area of the graph. The actual width of the resulting image will differ.

width-hint
Default value: none

Similar to height-hint, this controls the value used by Cricket for the width tag in its HTML output. See height-hint for more information.

y-max
Default value: none

Using this tag (and its counterpart, y-min) you can explicitly control the vertical scale of the graph and override any datasource-specific y-max or y-min tag defined in a graph dictionary entry. For example, these tags might be useful when you are graphing some value which should normally be very tightly bounded (for instance between 0 and 1 second), but occasionally jumps far above 1 second. In this case, all variation under one second will be essentially invisible on the graph, unless you set the y-min to 0 and the y-max to 1.

Using y-max or y-min, disables auto-scaling, and so you must explicitly set both of them (although they need not both be set in the same dictionary entry, y-min=0 could appear in a view --default-- entry). Note that for many situations, the default auto-scaling is reasonable.

If the y-max tag is not defined in view entries, but does appear in graph datasource-specific entries, y-max for the view will default to the maximum per-datasource y-max. Similarly, y-min for the view defaults to the minimum per-datasource y-min.

y-min
Default value: none

Refer to y-max.

The Color Dictionary

The color dictionary is used to configure Cricket's auto-color selection system and to map color names to HTML-style color specifications (without the pound sign). Every key, except for the special key --order-- is considered a color name, and should have a color specification. The special key --order-- is used to tell Cricket what order to assign the colors when doing auto-selection.

An HTML-style color specification consists of three hex numbers ranging from 00 to ff. The numbers represent the intensity of the red, green, and blue components of the final color. For an example of a correctly configured color dictionary, see the Defaults file in the root of the sample config tree.

The OID Dictionary

The OID dictionary is a convenient place to put OID's to make the config files more readable. Before any SNMP operation, the user-supplied OID is scanned. Any text parts of the OID that match an entry in the OID dictionary get replaced. Note that this is a separate process from variable expansion, for historical reasons. The keys and values in this table are separated by whitespace. Each entry starts with the keyword "OID".

In keeping with the conventions of ASN.1 and SNMP, OID names are case insensitive, must start with a letter, and can otherwise consist of letters and numbers.

The HTML Dictionary

The HTML dictionary holds components of the UI that is created by the grapher using HTML. Before it is used, the HTML dictionary is expanded with respect to itself.

auto-error
Default value: any errors generated on this page

Any runtime errors that the grapher finds while attempting to create the page are added to this tag. It is usually referenced in the page-footer.

Note that only errors that Cricket can find while it's running will show up here. Problems finding the Perl interpreter, or parsing grapher.cgi will still show up in the webserver's error log.

auto-long-version
Default value: the contents of the VERSION file

This is the complete version string, including the version number and the time it was packaged for release.

auto-short-version
Default value: the version number

This is simply the version number from the complete Cricket version string.

auto-title
Default value: the title

This tag is set by the grapher to the title it expects to use on this page. It is normally referenced in the head tag.

body-options
Default value: none

This tag is incorporated into the HTML body tag. This is the perfect place to set a background color. It's not useful for much else, unless you want your graphs to play a MIDI file in the background.

head
Default value: none

This is HTML that will be inserted between the <head> and </head> tags. It can be used to incorporate a cascading style sheet into the Cricket user interface. This would allow you to easily control aspects of the UI design that are hardcoded in the source code.

To incorporate a CSS called "cricket.css", use a head tag like this:

html  head
<link rel="stylesheet" src="cricket.css" type="text/css">
                  
page-footer
Default value: none

This is the HTML that will go on the bottom of the page. As Cricket is shipped, it includes a link to the RRD Tool website. Tobias Oetiker, the author of RRD Tool, has respectfully asked frontend authors to feature a link to RRD Tool. Please take into account his wishes as you edit this tag.

The version of page-footer in the sample-config directory refers to a tag called contact, which can be used to put sub-tree specific contact information into the bottom of the page. This is only meant as an example. You might choose to put completely different information at the bottom of the page.

page-header
Default value: none

This is the HTML that will go at the top of the page, after the <body> tag, but before the rest of the user interface. As shipped, it illustrates how you might put a company logo on each page.

The Event Dictionary

This dictionary holds information related to events. An event is some point in time which you'd like to mark on graphs so that later, you'll remember why it was that those graphs look the way they do. An event is named, just like a target or a datasource. The event names are used in the view dictionary tag called events. names are used in the target dictionary tag called events.

Each event should have the following tags set:

color

The color of the event. This should be the name of a color in the color dictionary.

date

The time the event happened, which controls where on a graph the vertical rule appears.

name

This is the text that will show up in the legend on the graph.

The RRA Dictionary

The RRA dictionary holds parameters used by Cricket when it is setting up a new RRD file. These parameters control how much data RRD stores, and on what schedule it consolidates high resolution data into lower resolution data.

The RRA dictionary that ships with Cricket in the sample config tree will rollup and retain data using the same schedule that MRTG 2.x has used for years. This has proven satisfactory for most users. If you change the polling rate from 5 minutes, this dictionary needs to be edited. Likewise, if you want to keep history farther back in time, you'll need to edit these entries.

Because this table is only used when a new RRD is being created, you would need to delete any existing datafile, losing all of the data in it, before a change to the RRA dictionary takes effect.

For more information on the format of these dictionary entries, read the RRD Tool documentation on the "create" command.

Cricket version 1.0.5, released 2004-03-28.

Copyright (C) 1998-2000 Jeff Allen. Cricket is released under the GNU General Public License.

cricket-1.0.5/doc/tools.html0100644000114300000000000001260510031605175014704 0ustar bertdwheel Running the Programs

Running the Programs

In this section, each script's functions and arguments are discussed. There is a set of common arguments that some of the scripts accept, and of course, each has its own idiosyncrasies, too.

The following arguments are common to most of the components of Cricket.

-logLevel

Default value: info (except grapher.cgi, where it defaults to warn)

The possible values are "error", "warn", "info", or "debug". Each setting includes the output created by any of the preceding settings. All diagnostic output goes to the standard error stream.

-base

Default value: $HOME/cricket-config

You can use this setting to use a different config tree. Experience shows, however, that it's simplest to just accept the defaults and install Cricket in an account of its own.

Here's a brief description of each program, and what arguments it takes.

collector

Collector is the script that runs every five minutes from cron in order to traverse the config tree, fetch data, and enter it into the RRD files. It's also used interactively to test configurations, and to convert old-style RRD files to RRD 1.x.x files.

The collector will process only the subtrees listed on the command line, unless there are none, in which case it will process the entire config tree.

Except during testing, collector is usually run from collect-subtrees, which takes care of creating the huge command lines collector sometimes needs, and does other housekeeping jobs. When testing a new subtree of the config-tree, you can use a command like "$HOME/cricket/collector -logLevel debug /new-tree". Once you are certain the subtree is functioning correctly, you can add it to the collect-subtrees config file, and rely on collect-subtrees to run the collector for you.

The collector will always run in two stages: collecting the data and checking thresholds for monitoring. If you for some reason do not need or want the threshold monitoring, you can specify the -skipMonitor option. Good reasons not to run this second phase include:

  • Cricket is first run, or has been down for more that your heartbeat interval, and you want to avoid threshold alarms because of NaN values.
  • You don't use threshold monitoring at all and want to avoid the overhead of scanning for it.
  • You run your collector more frequently than once every five minutes, and don't want the overhead for the "intermediate" runs.
grapher.cgi

Accepts common arguments.

The grapher is almost never run from the command line, so options parsing is basically a moot point. The CGI script should get run by the web server automatically in response to accesses to files that end in CGI. Consult your web server documentation to find out how to make it work like this.

Because it's not generally possible to control the command line of grapher.cgi, it's critical that it either defaults to the correct base directory, or that it has the base directory hardcoded internally to it. The default base directory is $HOME/cricket-config. However, $HOME will only be set right if Cricket can guess it's username from it's URL. If there's any doubt, you should hard code the base directory by editing the first few lines of grapher.cgi.

configure

Accepts no arguments at this time.

This is basically a placeholder for when Cricket is more complicated (say it ain't so!) and requires an autoconf-generated configure script. At this point, it can point all the Cricket scripts at your Perl install, which is very helpful for sites which do not have Perl installed in a standard location.

Cricket version 1.0.5, released 2004-03-28.

Copyright (C) 1998-2000 Jeff Allen. Cricket is released under the GNU General Public License.

cricket-1.0.5/doc/win2kw1.gif0100755000114300000000000000704707263447120014665 0ustar bertdwheelGIF89ak!,kH*\ȰÇHŋ3jȱǏ CIE&S\ɲI0cʜIK8s꤈rϟ@~)юD*]4eҦP>J*ԩVĪW\vٳdѪYv۷^$t܊w3潩^Mڶ ީDڝlw O1ǵw˄5w挒4ɘ 1qLkTݹȔkƊro͆bkvџf9!_7Nu]c`ܷo&nv֡Ϸu=tV|f} ߀ܩ~B߁ΖYB Ha]\rȗp)VfvM(.%&h,ڏ>!k W$'fq5,g3BPfTcTvh%^i)H]Re+=lflIYdyfhif2փ:I` *&[t(^. :z]; &k@fƝh;'_yZpG!lArn`wy.ʫ%bz7k"蒿zbj_ %z (K^*)q:_Baj8&cJ*+MɅWan k_[* SVj3Ʃ0\vLˢ{r_,h3] XBIGYO]Su:]_aYvR}UDlpTdmwu߭7y[-k]-24}-]bN9|k㮫OhyWʪt{&{cGq"?~;vK+un\w cߊJh}S^%cCҋ̩OtףF^T {V2*?U*M  (Z@!ljSd[p*?iT `d)E>>嫝}yWvLRVO:6fY5sCy ~h/ygpgFk 'Eqŀ7 ywfpu&oon%$X(H^&gٷ,i.([wv1(jE8p74 xtqE#eJ"$QrWqHCW=|$3$2UTw|YtRE/XDp~'u umWBnih+x-1w؇n!{waaEbćgq@x9$xhKH08fp?춃8XhΥ.zEUCDWh#v؄Nh1,53r7Yn85PgfIV~4|~k-zwdvr؍\-R"z'_XCTC1dbh(uuHMi~W;XW쨏撏(,(8 2c4:GӉȑɐh!hnxBؒ00/䖂8)^;cricket-1.0.5/doc/win2kwmi.html0100755000114300000000000004027107760440275015333 0ustar bertdwheel Installing Cricket on Win2K to Monitor WMI Counters

Installing Cricket on Win2K to Monitor WMI Counters

Windows 2000 includes Windows Management Instrumentation (WMI), an implementation of the Web-based Enterprise Management (WBEM) industry standard. Hardware, operating system, and application (such as SQL Server, and IIS) performance data counters are available through WMI.

Cricket is an open-source software tool originally designed for network monitoring and trend analysis on UNIX platforms. The source data monitored by Cricket is not restricted to network data and it has been successfully applied to monitor hosts and applications at WebTV. Cricket is written in Perl, and its underlying data storage technology, RRDtool, is available for Win32 platforms. Cricket includes a CGI component for monitoring access from any web browser.

This document describes the process of installing Cricket on Windows 2000, running Cricket with Internet Information Server (IIS), and using Cricket to collect data from WMI counters. It assumes some familiarity with Windows 2000, IIS, WMI, Perl Modules, and Cricket.

You may wish to review John Zola's document Installing Cricket on NT 4.0. He discusses some of the details omitted from this document (such as compiling RRDs).

The Materials List

Deploying Cricket on Windows 2000 to monitor WMI relies on a number of Perl packages and some external software. Fortunately, these packages and software are all available and compatible with Windows 2000. The following is a list of the components along with their provider.

Perl Packages:

Binaries:

Installation Instructions:

Before you begin, install Windows 2000 Server with IIS. You will need Microsoft Visual C++. These instructions assume the default configuration for IIS.

1. Create a monitoring account in the domain. Give this account administrator access on the monitoring machine (the machine that will run Cricket). On the remote hosts to be monitored, set the proper permissions for the monitoring account (this will require the assistance of your domain administrator).

a. On each remote host, the following WMI permissions need to granted to the monitoring account for the namespace that includes the counters you wish to monitor: Execute Methods, Provider Write, Enable Account, and Remote Enable (Windows 2000 sets the first three to allow by default for Everyone). This can be done through scripting, or the WMI Control management snap-in (run wmimgmt.msc on a Windows 2000 host).

b. WMI permissions are not sufficient alone to access some system objects/properties you may wish to monitor if these objects/properties are managed through the host or domain security policy. On the remote Windows 2000 computer, use the Local Security Policy control panel applet (under Administrative Tools). For example, you may need to change the settings on Profile System Performance under Local Policies\User Rights Assignment.

c. You can use the WMI program, wbemtest.exe (found in the %system32%\wbem directory) to confirm permissions are set correctly. If step (a) is not completed for a host, you will receive an access denied message. If step (b) is not completed, WMI queries will seem to work, but NULL or zero values will be returned (this can be hard to determine because some fields may legitimately be NULL or zero).

d. If you have security difficulties, one option is to make the monitoring account an administrator on each remote host it will monitor. But the "right thing to do" is for the monitoring account to be a regular domain user with just the additional permissions it needs for read only access to the WMI counters and objects to be monitored.

2. Login to the monitoring machine using the monitoring account.

3. Install Perl from the ActiveState distribution. Notes on the ActiveState installer: installing PerlScript and  Perl for ISAPI is not required. Enable the options registering Perl with the Web Server and adding the Perl bin directory to your path.

4. Install Berkeley DB. Following the instructions included with the distribution. Your goal is compile the DLL libdb31.dll. Once compiled, make certain this DLL is on your path..

5. Install Cricket. You may find John Zola's Installing Cricket on NT 4.0 helpful, although many of its specific suggestions are obsolete given the cricket-conf.pl configuration scheme introduced with Cricket 1.0.3.  The remainder of the document refers to the installation directory as <install dir> (gInstallRoot in the code). The cricket subdirectory of <install dir> contains the Cricket source code (gConfigRoot in the code).

a. Set Cricket environment variable settings in grapher.cgi, mini-graph.cgi, and cricket-conf.pl.  In grapher.cgi and mini-grapher.cgi, set programdir explicitly. In mini-graph.cgi you may also need to replace 'perl' with the complete path to the Perl executable (i.e. c:/perl/bin/perl) on the exec() call mini-graph.cgi uses to invoke grapher.cgi in the doGraph() subroutine. In cricket-conf.pl, set gCricketHome. You may also choose to set gConfigRoot and gCacheDir. Use forward slashes in pathnames.

6. Install the additional Perl packages. For Perl extensions (a package that includes a compiled DLL), the extension build must be compatible with the build of ActiveState installed. Currently, ActiveState builds 5xx are Perl 5.005 and ActiveState builds 6xx are Perl 5.6. The packages DB_File, Digest-MD5, and TimeDate are available at the ActiveState Perl Package Archive for both 5xx and 6xx ActiveState builds. They can be installed via the utility ppm. You will need to compile the source for the RRDs package after installing ActiveState. The source of RRDs is available from www.rrdtool.org. After compilation, manually install this packages to the site/lib subtree of your Perl installation. SNMP_Session package does not need to be compiled because it does not rely on an auxiliary DLL. After you download the archive, extract the *.pm files and install them in your site/lib subtree.

7. Configure IIS. Start the Internet Services Manager.

a. Create a virtual directory called crickethome. Set the local directory to <install dir>.

b. Register *.cgi files to be processed by the Perl executable. Right-click properties on the crickethome virtual directory. Disable read, write, and directory browsing. Set Execute Permissions to "None". Click the Configuration button. Look at the settings for the *.pl extension. Then create a new Application mapping for *.cgi and enter the same information.

c. Enable permissions on the cricket subdirectory of <install dir> Again, right-click properties. Grant read and set Execute Permissions to "Scripts and Executables".

d. Stop and restart the Default Web Site.

8. Create a scheduled task to run the Cricket script collect-subtrees (this the a wrapper script which invokes the Cricket collector and performs logging).

a. Click on Add Scheduled Task under Control Panel\Scheduled Tasks. Click through the wizard, choose a dummy application, give it the name "Cricket Collector", and choose a dummy run schedule. Enter the username and password the monitoring account. Enable the "Open advanced properties" check box and click Finish.

b. Now configure the real task. Enter a command line similar to the following (substituting paths on your system as appropriate):

c:\Perl\bin\perl <install dir>/cricket/collect-subtrees

Set the "Start in" field to <install dir>

c. Select the Schedule tab. Schedule the task daily; then click the Advanced button. Don't choose an End Date. Enable and complete the Repeat task section. Set the task to run every 5 minutes (or other interval, this should match the rrd-poll-interval you are using). Set the duration to 23 hours and 55 minutes (or 24 hours - whatever interval you are using).

d. Back on the Task tab, uncheck the Enabled checkbox.

9. Create and compile a Cricket config tree. Configure the subtree-sets file (in <install dir>\cricket). Refer to Cricket documentation and the next section on this document, Monitoring WMI Counters. After successful compilation, you may choose to run the collector manually to check for any errors.

10. Enable the scheduled task you created in step (8).

11. After a few minutes, check log files in <install dir>/cricket-logs to verify everything is running okay. Test the grapher on the monitoring machine with the URL: http://localhost/crickethome/cricket/grapher.cgi

Monitoring WMI Counters

Polling WMI counters is facilitated through the fetch module, wbem.pm. This diagram illustrates the layers of the data collection mechanism:

As described in the Cricket documentation, each data collection mechanism has a scheme, which describes a mapping between the ds-source tag of the data source dictionary entry and the list of arguments passed to the fetch module. For WBEM, the scheme is:

wbem:host:namespace:class:field:predicate

The arguments host and namespace are used to establish a connection to either the local or a remote machine. The arguments class, field, and predicate are use to construct a WQL query.

No username and password are specified for remote queries. The DCOM layer automatically substitutes the username and password of the monitoring account when a connection is established to a remote machine.

Host is a Win2K host name. Host should be blank or unspecified for the local machine.

Namespace refers to the WMI namespace to be queried. Most of the interesting WMI classes are under Root\CIMV2. Backslashes in the namespace specification must be escaped.

WQL is a language very similar to SQL for querying WMI classes. A WMI class can be thought of as a table, with one or more records, or instances. The attributes of the class are the table fields. Conceptually, the class, field, and predicate for a WBEM data source generate the query:

Select field from class where predicate

If the class of interest has only once instance, then the predicate argument can be omitted. If the class has multiple instances, you should specific a predicate to restrict the result to a single instance. Although Cricket supports multiple instances for SNMP data sources, this is not the case for WBEM data sources. Each data source must be mapped to exactly one WMI instance; in other words, each WQL query must be a singleton select.

Similar to the Cricket SNMP fetch modules, wbem.pm consolidates multiple requests to the same remote host and the same WMI class (table) as long as the data sources are within the same target (RRD file). Separate targets requiring remote connections to the same host will result in separate connections.

For example, suppose you wish to monitor both the ReadOperationCount and WriteOperationCount for some Win32_Process on the same machine. Here are the two ds-source entries:

wbem:host:Root\\CIMV2:Win32_Process:ReadOperationCount:Name='process.exe'
wbem:host:Root\\CIMV2:Win32_Process:WriteOperationCount:Name='process.exe'
      

If these data source are in the same target, wbem.pm will establish a single connection to host as username and issue the WQL query:

Select "ReadOperationCount", "WriteOperationCount" from "Win32_Process" where Name = 'process.exe'
      

If you are uncertain as to what can be monitored via WMI, you can use the WMI Object Browser included with the WMI SDK. You can test the semantics and syntax of WQL queries through wbemtest.exe, a utility included on all Win2K systems.

Acknowledgments

Thanks to Brian Gann, Cary Yee, Adam Meltzer, Jeff Allen, and John Zola. Questions or comments: contact Jake Brutlag.

cricket-1.0.5/lib/0040755000114300000000000000000010031605166012656 5ustar bertdwheelcricket-1.0.5/lib/Bundle/0040755000114300000000000000000010031605163014064 5ustar bertdwheelcricket-1.0.5/lib/Bundle/CricketPrereq.pm0100644000114300000000000000115707251450035017174 0ustar bertdwheelpackage Bundle::CricketPrereq; $VERSION = sprintf("%d.%02d", q$Revision: 1.4 $ =~ /(\d+)\.(\d+)/); 1; __END__ =head1 NAME Bundle::CricketPrereq - A bundle to install all the modules Cricket needs. =head1 SYNOPSIS perl -MCPAN -e 'install Bundle::CricketPrereq' =head1 CONTENTS Digest::MD5 2.09 - Message Digest 5 LWP 5.48 - library for WWW access in Perl DB_File 1.73 - access to Berkeley DB 1.x Date::Parse 2.11 - parses dates Time::HiRes 01.20 - high resolution timing =head1 DESCRIPTION This bundle defines all reqreq modules for Cricket. =head1 AUTHOR Jeff R. Allen =cut cricket-1.0.5/lib/Monitor.pm0100644000114300000000000006045210015156417014651 0ustar bertdwheel# Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Javier Muniz and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. package Monitor; use strict; use snmpUtils; use RRD::File; use Common::Log; use Common::Util; # Registered monitors $Common::global::gMonitorTable{'value'} = \&monValue; $Common::global::gMonitorTable{'hunt'} = \&monHunt; $Common::global::gMonitorTable{'relation'} = \&monRelation; $Common::global::gMonitorTable{'exact'} = \&monExact; # Support for aberrant behavior detection $Common::global::gMonitorTable{'failures'} = \&monFailures; $Common::global::gMonitorTable{'quotient'} = \&monQuotient; # NaN Alarm processing # Return value of nanErr = 1 is not an alarm or threshold pass # Return value of nanErr = 0 is an alarm or threshold failure my $nanErr = 1; if ($Common::global::gEnableNoValueAlarms) { $nanErr = ($Common::global::gEnableNoValueAlarms) ? 0 : 1; } sub new { my($package) = @_; my $self = { }; bless $self, $package; return $self; } sub monValue { my($self,$target,$ds,$type,$args) = @_; my($min,$max,$minOK,$maxOK); my(@Thresholds) = split(/\s*:\s*/, $args); my($value) = $self->rrdFetch( $target->{'rrd-datafile'}, $self->getDSNum($target, $ds), 0 ); if (!defined($value)) { Warn("Monitor: Couldn't fetch last $ds value from " . $target->{'rrd-datafile'}."."); return 1; } return ($nanErr, 'NaN') if isNaN($value); $min = shift(@Thresholds); $min = 'n' if (! defined($min)); if (lc($min) eq 'n') { $minOK = 1; } else { $minOK = ($value > $min) ? 1 : 0; } $max = shift(@Thresholds); $max = 'n' if (! defined($max)); if (lc($max) eq 'n') { $maxOK = 1; } else { $maxOK = ($value < $max) ? 1 : 0; } Debug ("Value is $value; min is $min; max is $max"); return ($maxOK && $minOK,$value); } sub monHunt { my($self,$target,$ds,$type,$args) = @_; my($roll, $cmp_name, $cmp_ds) = split(/\s*:\s*/, $args); if (!defined($roll)) { Warn("Monitor: Missing rollover value in hunt threshold for " . $target->{'auto-target-name'} . " datasource $ds."); return 1; } # Fetch the current value from target's rrd file my($value) = $self->rrdFetch($target->{'rrd-datafile'}, $self->getDSNum($target, $ds), 0); if (!defined($value)) { Warn("Monitor: Couldn't fetch last $ds value from " . "$target->{'rrd-datafile'}."); return 1; } if ($value == 0) { return 1; # no rollover, test succeeded. } my($cmp_target); if (defined($cmp_name)) { $cmp_name = join('/',$target->{'auto-target-path'},$cmp_name) if (!($cmp_name =~ /^\//)); $cmp_target = $Common::global::gCT->configHash(lc($cmp_name), 'target'); if (defined($cmp_target)) { ConfigTree::Cache::addAutoVariables(lc($cmp_name), $cmp_target, $Common::global::gConfigRoot); ConfigTree::Cache::expandHash($cmp_target, $cmp_target, \&Warn); } else { Warn("Monitor: No such target: $cmp_name"); return 1; } } else { $cmp_target = $target; } $cmp_ds = $ds unless ($cmp_ds); my($cmp_value) = $self->rrdFetch($cmp_target->{'rrd-datafile'}, $self->getDSNum($cmp_target, $cmp_ds), 0); if (!defined($cmp_value)) { Warn("Monitor: Couldn't fetch current value from $cmp_name"); return 1; } return ($cmp_value >= $roll); } # if X and Y are data sources (Y is possibly X shifted by some temporal offset) # and Z is a threshold, then a relation monitor checks: # abs(Y - X)/Y > Z for Z a percentage (marked pct) # abs(Y - X) > Z for Z not a percentage # Default > can be replaced with < # return 1 if the check passes or error, 0 if fail sub monRelation { my($self, $target, $ds, $type, $args) = @_; my($thresh, $cmp_name, $cmp_ds, $cmp_time) = split(/\s*:\s*/, $args); my($pct) = ($thresh =~ s/\s*pct\s*//i); $cmp_time = 0 unless(defined($cmp_time)); if (!defined($thresh) || !defined($cmp_time)) { Warn("Monitor: Improperly formatted relation monitor for " . "$target->{'auto-target-name'} datasource $ds."); return 1; } my($gtlt); if (substr($thresh,0,1) eq '<' || substr($thresh,0,1) eq '>') { $gtlt = substr($thresh,0,1); $thresh = substr($thresh,1); } else { $gtlt = '>'; } # Fetch the current value from target's rrd file my($value) = $self->rrdFetch($target->{'rrd-datafile'}, $self->getDSNum($target,$ds), 0); if (!defined($value)) { Warn("Monitor: Couldn't fetch last $ds value from " . "$target->{'rrd-datafile'}."); return 1; } return ($nanErr, 'NaN') if isNaN($value); my ($cmp_value); # Is this a straight comparison value or should we fetch it as a DS if ($cmp_ds =~ /^[-+]?([0-9]*\.[0-9]+|[0-9]+)$/) { $cmp_value = $cmp_ds; # Fetch the value it must be a DS } else { ($cmp_value) = $self -> FetchComparisonValue($target,$ds,$cmp_name,$cmp_ds,$cmp_time); return ($nanErr,'NaN') if isNaN($cmp_value); } my($difference) = abs($cmp_value - $value); $thresh = abs($thresh); # differences are always positive if ($pct) { # threshold is a percentage if ($cmp_value == 0) { #avoid division by 0 if ($difference == 0 && $gtlt eq '<') { return (1,$value); } else { return (0,$value); } } $difference = $difference / abs($cmp_value) * 100; } Debug("Values $value, $cmp_value; Difference is $difference; " . "gtlt is $gtlt; thresh is $thresh."); # see documentation: threshold fails if expression is false return (0,$value) if ((eval "$difference $gtlt $thresh") == 0); return (1,$value); } sub monExact { my($self,$target,$ds,$type,$args) = @_; my(@args) = split(/\s*:\s*/, $args); if (@args != 1) { Warn("Monitor: monitor type \"exact\" requires 1 argument for " . "$target->{'auto-target-name'} $ds."); return 1; } my $exact = shift @args; my($value) = $self->rrdFetch( $target->{'rrd-datafile'}, $self->getDSNum($target, $ds), 0 ); if (!defined($value)) { Warn("Monitor: Couldn't fetch last $ds value from " . "$target->{'rrd-datafile'}."); return 1; } return ($nanErr,'NaN') if isNaN($value); return $exact eq $value ? 0 : 1; } # check the FAILURES array for failure recorded by the aberrant behavior # detection algorithm in RRD # Return values: 1 = success # 0 = failure # Returns 1 on default or unexpected error sub monFailures { my($self, $target, $ds, $type, $args) = @_; # only check value range if specified, as this requires fetching from the RRD if ($args =~ /:/) { # monValue returns 0 if out of range, 1 otherwise my ($rc, $value) = $self->monValue($target,$ds,'value',$args); Debug("value is $value, in range $rc"); # value is out of range, so ignore any failure alarm if ($rc == 0) { return 1 }; } my $datafile = $target->{'rrd-datafile'}; my $dsNum = $self->getDSNum($target, $ds); return 1 if (!defined($dsNum)); return 1 if (!defined($datafile)); # open the file if not already open if (!defined($self->{currentfile}) || $datafile ne $self->{currentfile}) { my($rrd) = new RRD::File( -file => $datafile ); return 1 if (!$rrd->open() || !$rrd->loadHeader()); $self->{currentfile} = $datafile; $self->{openrrd} = $rrd; } # check for a valid $dsNum return 1 unless ($dsNum < $self->{openrrd}->ds_cnt()); # find the FAILURES RRA my $rra; my $rraNum; for($rraNum = 0; $rraNum <= $#{$self->{openrrd}->{rra_def}}; $rraNum++) { $rra = $self->{openrrd}->rra_def($rraNum); last if ($rra -> {rraName} eq 'FAILURES'); } return 1 if ($rra -> {rraName} ne 'FAILURES'); # retrieve the most recent value my $ret; $ret = $self->{openrrd}->getDSRowValue($rraNum,0,$dsNum); if (!defined($ret)) { Warn("Monitor: Couldn't fetch last $ds value from " . "$target->{'rrd-datafile'} FAILURES RRA."); return 1; } # FAILURES array stores a 1 for a failure (so should return 0) return (1,'NaN') if isNaN($ret); return !($ret); } # if X and Y are data sources (Y is possibly X shifted by some temporal offset) # and Z is a threshold, then a quotient monitor checks: # X/Y > Z for Z a percentage (marked pct) # abs(Y - X) > Z for Z not a percentage (deprecated) # Default > can be replaced with < # return 1 if the check passes or error, 0 if fail sub monQuotient { my($self, $target, $ds, $type, $args) = @_; my($thresh, $cmp_name, $cmp_ds, $cmp_time) = split(/\s*:\s*/, $args); my($pct) = ($thresh =~ s/\s*pct\s*//i); $cmp_time = 0 unless(defined($cmp_time)); Info("Monitor: Use of quotient monitors without percent threshold is " . "deprecated for $target->{'auto-target-name'} datasource $ds.") unless (defined($pct)); if (!defined($thresh) || !defined($cmp_time)) { Warn("Monitor: Improperly formatted quotient monitor for " . "$target->{'auto-target-name'} datasource $ds."); return 1; } my($gtlt); if (substr($thresh,0,1) eq '<' || substr($thresh,0,1) eq '>') { $gtlt = substr($thresh,0,1); $thresh = substr($thresh,1); } else { $gtlt = '>'; } # Fetch the current value from target's rrd file my($value) = $self->rrdFetch($target->{'rrd-datafile'}, $self->getDSNum($target,$ds), 0); if (!defined($value)) { Warn("Monitor: Couldn't fetch last $ds value from " . "$target->{'rrd-datafile'}."); return 1; } return ($nanErr,'NaN') if isNaN($value); my ($cmp_value); # Is this a straight comparison value or should we fetch it as a DS if ($cmp_ds =~ /^[-+]?([0-9]*\.[0-9]+|[0-9]+)$/) { $cmp_value = $cmp_ds; # Fetch the value it must be a DS } else { ($cmp_value) = $self -> FetchComparisonValue($target,$ds,$cmp_name,$cmp_ds,$cmp_time); return ($nanErr,'NaN') if isNaN($cmp_value); } my($difference) = abs($cmp_value - $value); $thresh = abs($thresh); # differences are always positive if ($pct) { # threshold is a percentage if ($cmp_value == 0) { # avoid division by 0 if ($difference == 0 && $gtlt eq '>') { return 1; } else { return 0; } } $difference = abs($value/$cmp_value) * 100; } Debug("Difference is $difference; gtlt is $gtlt; thresh is $thresh."); return (0,$value) if (eval "$difference $gtlt $thresh"); return (1,$value); } # shared code used by monRelation and monQuotient sub FetchComparisonValue { my($self,$target,$ds,$cmp_name,$cmp_ds,$cmp_time) = @_; my($cmp_target); if (! $cmp_name) { $cmp_target = $target; } else { $cmp_name = join('/',$target->{'auto-target-path'}, $cmp_name) if (!($cmp_name =~ /^\//)); $cmp_target = $Common::global::gCT->configHash(lc($cmp_name), 'target'); if (defined($cmp_target)) { ConfigTree::Cache::addAutoVariables(lc($cmp_name), $cmp_target, $Common::global::gConfigRoot); ConfigTree::Cache::expandHash($cmp_target, $cmp_target, \&Warn); } else { Warn("Monitor: No such target: $cmp_name"); return 'NaN'; } } $cmp_ds = $ds unless ($cmp_ds); my($cmp_value) = $self->rrdFetch($cmp_target->{'rrd-datafile'}, $self->getDSNum($cmp_target,$cmp_ds), $cmp_time); if (!defined($cmp_value)) { Warn("Monitor: Couldn't fetch value for $cmp_time ". "seconds ago from $cmp_name."); return 'NaN'; } if (isNaN($cmp_value)) { Info("Monitor: Data for $cmp_time seconds ago from ". "$cmp_name is NaN"); return 'NaN'; } return $cmp_value; } # fetches any data you might need from a rrd file # in a RRA with consolidation function AVERAGE # caching the open RRD::File object for later use # if possible. sub rrdFetch { my($self,$datafile,$dsNum,$sec) = @_; # check all of our arguments; return if (!defined($dsNum)); return if (!defined($sec)); return if (!defined($datafile)); Debug("in rrdFetch: file is $datafile"); if (!defined($self->{currentfile}) || $datafile ne $self->{currentfile}) { my($rrd) = new RRD::File( -file => $datafile ); return if (!$rrd->open() || !$rrd->loadHeader()); $self->{currentfile} = $datafile; $self->{openrrd} = $rrd; } return unless ($dsNum < $self->{openrrd}->ds_cnt()); my($rra,$lastrecord,$rraNum); for($rraNum = 0; $rraNum <= $#{$self->{openrrd}->{rra_def}}; $rraNum++) { $rra = $self->{openrrd}->rra_def($rraNum); # if the consolidation function is not AVERAGE, we can # skip this RRA Debug("in rrdFetch: skipping RRA"); next if ($rra->{rraName} ne "AVERAGE"); $lastrecord = $rra->{row_cnt} * $rra->{pdp_cnt} * $self->{openrrd}->pdp_step(); last if ($lastrecord >= $sec); } if ($lastrecord < $sec) { Warn("Monitor: $datafile does not have data going back " . "$sec seconds."); return; } if ($sec % ($lastrecord / $rra->{row_cnt})) { Warn("Monitor: RRA required to find data $sec seconds ago " . "is too granular."); return; } my($rowNum) = $sec / ($self->{openrrd}->pdp_step() * $rra->{pdp_cnt}); Debug("in rrdFetch: rraNum is $rraNum rowNum is $rowNum dsNum is $dsNum"); my ($foo) = $self->{openrrd}->getDSRowValue($rraNum,$rowNum,$dsNum); Debug("in rrdFetch: return is $foo"); return $self->{openrrd}->getDSRowValue($rraNum,$rowNum,$dsNum); } # Given a target reference and datasource name, # returns the datasource number or undef if no # datasource of that name can be found in target's # target-type dictionary sub getDSNum { my($self, $target, $dsName) = @_; my($ttRef) = $Common::global::gCT->configHash( join('/',$target->{'auto-target-path'}, $target->{'auto-target-name'}), 'targettype', lc($target->{'target-type'}), $target); my($Counter) = 0; my(%dsMap) = map { $_ => $Counter++ } split(/\s*,\s*/,$ttRef->{'ds'}); return $dsMap{$dsName}; } # Subroutines to handle alarms sub dispatchAlarm { # order of arguments: $self, $target, $name, $ds, $type, $threshold, # $alarmType, $alarmArgs, $val my ($args, $action) = @_; my ($target, $ds, $val) = ($$args[1], $$args[3], $$args[8]); my $alarmType = $$args[6]; my %dispatch = ( EXEC => \&alarmExec, FILE => \&alarmFile, FUNC => \&alarmFunc, MAIL => \&alarmMail, SNMP => \&alarmSnmp, META => \&alarmMeta, ); my $alarm = $dispatch{$alarmType}; unless (defined($alarm)) { Warn("Monitor: unknown alarm $alarmType for target: ". $target->{'auto-target-path'} . $target->{'auto-target-name'} . " for datasource $ds"); return; } my $return = $alarm->($args, $action); return; }; # Process alarm action sub Alarm { # order of arguments: $self, $target, $name, $ds, $type, $threshold, # $alarmType, $alarmArgs, $val my $action = 'ADD'; my $return = \&dispatchAlarm(\@_, $action); return; }; # Action to clear an alarm sub Clear { # order of arguments: $self, $target, $name, $ds, $type, $threshold, # $alarmType, $alarmArgs, $val my $action = 'CLEAR'; my $return = \&dispatchAlarm(\@_, $action); return; }; sub alarmExec { my ($args, $action) = @_; my $alarmArgs = $$args[7]; system($alarmArgs->[0]); if ($action eq 'ADD') { Info("Monitor: Triggered event with system command '". $alarmArgs->[0]."' ."); } else { Info("Monitor: Cleared event with shell command '". $alarmArgs->[1]."' ."); } return; }; sub alarmFile { my ($args, $action) = @_; my ($self, $target) = ($$args[0], $$args[1]); my ($name, $ds) = ($$args[2], $$args[3]); my ($type, $threshold) = ($$args[4], $$args[5]); my ($alarmArgs, $val) = ($$args[7], $$args[8]); $self->LogToFile($alarmArgs, $action, $name, $ds, $val); return; }; sub alarmFunc { my ($args, $action) = @_; my $alarmArgs = $$args[7]; if (defined $main::gMonFuncEnabled) { if ($action eq 'ADD') { eval($alarmArgs->[0]); Info("Monitor: Triggered event with FUNC '".$alarmArgs->[0]."' ."); } elsif ($action eq 'CLEAR') { eval($alarmArgs->[1]); Info("Monitor: Cleared event with FUNC '".$alarmArgs->[1]."' ."); } } else { Warn("Monitor: Exec triggered, but executable alarms are not enabled."); } return; }; sub alarmMail { my ($args, $action) = @_; my ($self, $target) = ($$args[0], $$args[1]); my ($name, $ds) = ($$args[2], $$args[3]); my ($type, $threshold) = ($$args[4], $$args[5]); my ($alarmArgs, $val) = ($$args[7], $$args[8]); $self->sendEmail( $action, $alarmArgs, $type, $threshold, $name, $ds, $val, $target->{'inst'} ); return; }; sub alarmSnmp { my ($args, $action) = @_; my ($self, $target) = ($$args[0], $$args[1]); my ($name, $ds) = ($$args[2], $$args[3]); my ($type, $threshold) = ($$args[4], $$args[5]); my ($val) = ($$args[8]); my $Specific_Trap_Type = $action eq 'ADD' ? 4 : 5; $self->sendMonitorTrap( $target, $Specific_Trap_Type, $type, $threshold, $name, $ds, $val ); return; }; sub alarmMeta { my ($args, $action) = @_; # No action is required. # Alarm data is already stored in the Cricket meta files by HandleTarget.pm # The meta data is stored in $CRICKET/cricket-data/ directories. return; }; # Attempt to send an alarm trap for a given target sub sendMonitorTrap { my($self,$target,$spec,$type,$threshold,$name,$ds,$val) = @_; my $to = $target -> {'trap-address'}; if (!defined($to)) { Warn("Monitor: No trap address defined for $target, couldn't send trap."); Info("Monitor: Threshold Failed: $threshold for target $target"); return; } my($OID_Prefix) = '.1.3.6.1.4.1.2595.1.3'; # OID for Cricket Violations my(@VarBinds); push(@VarBinds, "${OID_Prefix}.1", 'string', $type); push(@VarBinds, "${OID_Prefix}.2", 'string', $threshold); # name is the fully qualified target name push(@VarBinds, "${OID_Prefix}.3", 'string', $name); push(@VarBinds, "${OID_Prefix}.4", 'string', $ds); my($logName) = "cricket"; if (!Common::Util::isWin32() && defined($ENV{'LOGNAME'})) { $logName = $ENV{'LOGNAME'}; } push(@VarBinds, "${OID_Prefix}.5", 'string', $logName); # Common::HandleTarget overloads this tag # for scalar targets, it could be "" or 0 # otherwise, it is set the instance number if ($target->{'inst'}) { push(@VarBinds, "${OID_Prefix}.6", 'string', $target->{'inst'}); } if (defined($target->{'inst-name'})) { push(@VarBinds, "${OID_Prefix}.7", 'string', $target->{'inst-name'}); } # send the html contact-name my $htmlRef = $Common::global::gCT -> configHash($name,'html'); if (defined($htmlRef -> {'contact-name'})) { # parse the mailto tag my $tag = $htmlRef -> {'contact-name'}; if ($tag =~ /mailto\:([A-Z,a-z,.,@]+)/) { push(@VarBinds, "${OID_Prefix}.8", 'string', $1); } } push(@VarBinds, "${OID_Prefix}.9", 'string', $val) unless (!defined($val)); Info("Monitor: Trap Sent to $to:\n ". join(' -- ',@VarBinds)); snmpUtils::trap2($to,$spec,@VarBinds); } sub LogToFile { my ($self, $alarmArgs, $action, $targetName, $dataSourceName) = @_; my $filePath = $alarmArgs->[0]; my @lines = (); my $targetLine; return unless ($action eq 'ADD' || $action eq 'CLEAR'); # try to open for read first and hunt for duplicate lines my $bFound = 0; if (open(INFILE, "$filePath")) { $targetLine = $targetName . " " . $dataSourceName; while () { chomp; if ($_ eq $targetLine) { $bFound = 1; # nothing to add last if ($action eq 'ADD'); } else { push (@lines, $_) if ($action eq 'CLEAR'); } } } unless (open(INFILE, "+>>$filePath")) { Info("Monitor: Failed to open file $filePath: $!"); return; } # append the new line to the end of the file if ($action eq 'ADD' && $bFound == 0) { Info("Monitor: Appending line $targetLine to $filePath"); print INFILE $targetLine . "\n"; close(INFILE); return; } # don't need INFILE anymore close(INFILE); if ($action eq 'ADD' && $bFound == 1) { Info("Monitor: $targetName datasource $dataSourceName already in ". "file $filePath"); return; } if ($action eq 'CLEAR' && $bFound == 0) { Info("Monitor: $targetName datasource $dataSourceName already " . "deleted from file $filePath"); return; } # need to print out @lines, which excludes the $targetLine # overwrite old file unless (open(OUTFILE, ">$filePath")) { Info("Monitor: Failed to open file $filePath: $!"); return; } Info("Monitor: Deleting $targetLine from $filePath"); print OUTFILE join("\n", @lines); print OUTFILE "\n" unless (scalar(@lines) == 0); close(OUTFILE); } sub sendEmail { my($self, $spec, $alarmArgs, $type, $threshold, $target, $ds, $val, $inst) = @_; my $to = $alarmArgs -> [1]; if (!defined($to)) { Warn("Monitor: No destination address defined for $target, " . "couldn't send email."); return; } my @Message; push @Message, "type:\t\t$type"; push @Message, "threshold:\t$threshold"; push @Message, "target:\t$target"; push @Message, "ds:\t\t$ds"; if (defined($val)) { push @Message, "val:\t\t$val"; } # for scalar targets, inst is either "" or 0 if ($inst) { push @Message, "inst:\t\t$inst"; } my $mail_program = $alarmArgs -> [0]; if (!defined($mail_program)) { Warn("Monitor: No email-program defined. Not sending email"); return; } elsif (!open(MAIL, "|$mail_program -s 'Cricket $spec: $target' $to\n")) { Warn("Monitor: Failed to open pipe to mail program: $!"); return; } Debug("|$mail_program -s 'Cricket $spec: $target' $to\n"); print (MAIL join ("\n", @Message)); Info("Monitor: Email sent to: $to\n" . join(' -- ', @Message)); close(MAIL); } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/lib/RPN.pm0100644000114300000000000000510607367411212013657 0ustar bertdwheel# -*- perl -*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # a simple little RPN calculator -- implements the same operations # that RRDTool does. package RPN; sub new { my($type) = @_; my($self) = {}; bless($self, $type); return $self; } sub op { my($self, $op) = @_; if ($op eq '+') { my($a) = $self->pop(); my($b) = $self->pop(); return unless (defined($a) && defined($b)); $self->push($b + $a); } elsif ($op eq '-') { my($a) = $self->pop(); my($b) = $self->pop(); return unless (defined($a) && defined($b)); $self->push($b - $a); } elsif ($op eq '*') { my($a) = $self->pop(); my($b) = $self->pop(); return unless (defined($a) && defined($b)); $self->push($b * $a); } elsif ($op eq '/') { my($a) = $self->pop(); my($b) = $self->pop(); return unless (defined($a) && defined($b)); if ($a != 0) { $self->push($b / $a); } else { $self->push(undef); } } elsif ($op =~ /^LOG$/i) { my($a) = $self->pop(); return unless (defined($a)); if ($a != 0) { $self->push(log($a)); } else { $self->push(undef); } } } sub pop { my($self) = @_; my($res) = pop(@{$self->{'stack'}}); warn("Stack underflow") if (! defined($res)); return $res; } sub push { my($self, @items) = @_; push @{$self->{'stack'}}, @items; } sub run { my($self, $string) = @_; my($item); foreach $item (split(/,/, $string)) { if ($item !~ /\d/ && ($item =~ /^[\+\*\/\-]/ || $item =~ /^log$/i)) { $self->op($item); } else { $self->push($item); } } return ($self->pop()); } 1; cricket-1.0.5/lib/exec.pm0100644000114300000000000000553107365656143014162 0ustar bertdwheel# -*-perl-*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. use Common::Log; $main::gDSFetch{'exec'} = \&execFetch; sub execFetch { # This procedure is passed a REFERENCE to an array of exec datasources. # The line consists of "index:line-num:shell command" # # There can and will be spaces in the shell command. If line-num # is missing it is assumed to be the first output line. # # VERY IMPORTANT: The index MUST be returned with the corresponding value, # otherwise it'll get put back into the wrong spot in the RRD. my($dsList, $name, $target) = @_; my(@results, %shellCmds); my($line); foreach $line (@{$dsList}) { my(@components) = split(/:/, $line, 3); my($index, $outputIndex, $cmd); if ($#components+1 == 3) { ($index, $outputIndex, $cmd) = @components; } elsif ($#components == 1) { ($index, $cmd) = @components; $outputIndex = 0; } else { Error("Malformed datasource source: $line."); return (); } push(@{ $shellCmds{$cmd} }, "$index:$outputIndex"); } my($cmd, $execDSRef); while (($cmd, $execDSRef) = each %shellCmds) { Info("Retrieving data (EXEC: $cmd) for " . $target->{'auto-target-name'}); if ( open(COMMAND, "$cmd|") ) { my(@output); chomp(@output = ); Debug("EXEC: $cmd results = " . join(",", @output)); while ( $line = shift @{ $execDSRef } ) { my($index, $outputIndex) = split(/:/, $line, 2); if (defined $output[$outputIndex]) { push(@results, "$index:$output[$outputIndex]"); } else { push(@results, "$index:U"); } } } else { Error("Could not retrieve data for $target->{'tname'} " . "(EXEC: $cmd): $!."); } } return @results; } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/lib/field.pm0100644000114300000000000000743007365656143014321 0ustar bertdwheel# -*-perl-*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # # Ripped off lock, stock, and barrel from JRA's file.pm. # - ejt 08/08/1999 use Common::Log; $main::gDSFetch{'field'} = \&fieldFetch; sub fieldFetch { # This procedure is passed a REFERENCE to an array of file datasources. # Each line consists of "index:filename:key:valuef:keyf:delim" # # There can be spaces in the key, but there probably won't be. # "valuef" defaults to "2" if not specified # "keyf" defaults to "1" if not specified # "delim" defaults to ":" if not specified # # VERY IMPORTANT: The index MUST be returned with the corresponding value, # otherwise it'll get put back into the wrong spot in the RRD. my($dsList, $name, $target) = @_; my(@results, %files); my($line); foreach $line (@{$dsList}) { my(@components) = split(/:/, $line, 6); my ($index, $file, $key, $valuef, $keyf, $delim); if ($#components+1 < 3) { Error("Malformed datasource source: $line."); return (); } $index = shift(@components); $file = shift(@components); $key = shift(@components); $valuef = shift(@components) || 2; $keyf = shift(@components) || 1; $delim = shift(@components) || ":"; push(@{ $files{$file} }, "$index:$file:$key:$valuef:$keyf:$delim"); } my($file, $ilRef, $il); while (($file, $ilRef) = each %files) { Info("Reading data from $file for " . $target->{'auto-target-name'}); if (open(F, "<$file")) { my(@lines); chomp(@lines = ); close(F); while ($il = shift @{ $ilRef } ) { my($index, $file, $key, $valuef, $keyf, $delim) = split(/:/, $il, 6); my $matches = 0; my $match = 0; foreach my $line (@lines) { my @bits = split($delim, $line); # just skip lines with too few fields next if (($#bits < $keyf - 1) || ($#bits < $valuef - 1)); if ($bits[$keyf - 1] eq $key) { $matches++; $match = $bits[$valuef - 1]; } } if ($matches > 1) { push @results, "$index:U"; Error("Key $key matched $matches times for " . $target->{'auto-target-name'} . " from file $file."); } elsif ($matches == 0) { push @results, "$index:U"; } else { push @results, "$index:$match"; } } } else { Error("Could not fetch data for " . $target->{'auto-target-name'} . " from file $file: $!."); } } return @results; } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/lib/file.pm0100644000114300000000000000541607365656143014157 0ustar bertdwheel# -*-perl-*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. use Common::Log; $main::gDSFetch{'file'} = \&fileFetch; sub fileFetch { # This procedure is passed a REFERENCE to an array of file datasources. # The line consists of "index:line-num:shell command" # # There can and will be spaces in the shell command. If line-num # is missing it is assumed to be the first output line. # # VERY IMPORTANT: The index MUST be returned with the corresponding value, # otherwise it'll get put back into the wrong spot in the RRD. my($dsList, $name, $target) = @_; my(@results, %files); my($line); foreach $line (@{$dsList}) { my(@components) = split(/:/, $line, 3); my($index, $lineno, $file); if ($#components+1 == 3) { ($index, $lineno, $file) = @components; } elsif ($#components == 1) { ($index, $file) = @components; $lineno = 0; } else { Error("Malformed datasource source: $line."); return (); } push(@{ $files{$file} }, "$index:$lineno"); } my($file, $ilRef, $il); while (($file, $ilRef) = each %files) { Info("Reading data from $file for " . $target->{'auto-target-name'}); if (open(F, "<$file")) { my(@lines); chomp(@lines = ); close(F); while ($il = shift @{ $ilRef } ) { my($index, $lineno) = split(/:/, $il, 2); if (defined($lines[$lineno])) { push @results, "$index:$lines[$lineno]"; } else { push @results, "$index:U"; } } } else { Error("Could not fetch data for " . $target->{'auto-target-name'} . " from file $file: $!."); } } return @results; } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/lib/func.pm0100644000114300000000000000464107706035304014157 0ustar bertdwheel# -*-perl-*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. use Common::Log; # Uncomment the following line if you want to enable 'FUNC' alarm triggers. # $main::gMonFuncEnabled = 1 ; $main::gDSFetch{'func'} = \&funcFetch; #---------------------------------------- # functions must be specified in the Defaults file similar to the # following: # "func:Package::function(arguments)" # # If a package is specified, it will be 'require'd. Unlike, say, 'exec', # this function will be called for each datasource, so if you have a function # that you'd like to return multiple values, write it such that it returns # each of the values in turn, or code the return values based on the arguments sub funcFetch { my($dsList, $name, $target) = @_; warn "(dsList,name,target) ([$dsList],[$name],[$target])\n"; my(@results); my($line); foreach $line (@{$dsList}) { if ( $line =~ /(\d*):(.*)/) { my ($res,$number,$call,$eval); ($number,$call) = ($1,$2); # we're trying to retrieve just the sub name, with no arguments my $sub = $call; $sub =~ s/(\(|\s).+//; if ($sub =~ /::/) { # if the sub is package-qualified... my $package = $sub; $package =~ s/::[^:]+$//; # trim off the sub name $eval = qq(use $package; $call); } else { $eval = $call; } $result = eval $eval; if ($@) { # there was an error of some kind Warn("Failed to collect info from $call: $@"); } else { push @results, "$number:$result"; } } } return @results; } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/lib/perfmon.pm0100755000114300000000000002570007770173175014706 0ustar bertdwheel# -*-perl-*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # This is still very early in development. It's designed to collect Perfmon # counters from Windows NT systems. Perfmon is similiar in many respects to # the way Cricket does things. It keeps a config file resident in memory # with all sorts of definitions from counter scaling, to what type of # counter it is. While it makes things convenient, it also requires a whole # bunch of disassembly of it's scheme to effectively put it into Cricket. # # The math seems to be very wrong for some counters, which seems odd as all # the formulas were ripped straight from Microsoft's documentation on the # perflib SDK at: # http://msdn.microsoft.com/library/en-us/perfmon/hh/pdh/perfdata_6di9.asp # # I'm continually developing this as we need a good solution in-house for # flexible historical trending of perfmon counters. # # I realize how god-awful slow this is, and it's really Perfmon's fault. # Most counters are durational and require some sort of wait-time to get # two instances loaded to to comparisons/time based calculations. Also, # every time Perfmon is initiated on a system, you're required to get a # list of every counter on the system which is very time consuming. Blame # my employer, not me! I'm considering making this application threaded to # boost performance. # # Bugs I'm currently aware of: # * Yes there are many missing counter types. This is being worked on. # I'd like to think the ones which are missing right now wouldn't be # commonly used anyway (PERF_AVERAGE_BULK) or wouldn't be relevant # to Cricket anyway (PERF_COUNTER_TEXT). # * I know PERF_RAW_FRACTION and PERF_RAW_BASE are supremely messed up # right now and I'm not sure why. I'm using the formulas Microsoft # defined on their web site (100*x/b) and it returns screwy results. # * On the same vein, this breaks the instance checker which Warn()s if # there are more than one matches for a selected counter. # * That's all I can think of right now. After spending over a week # working on this I think for the most part it works pretty well! # # If you find any glaring bugs, omissions, or errors please feel free to # email me so I can fix it. # # Thanks - Adam Meltzer # Minidoc: # # Perfmon counters use the following syntax for input: # perfmon:hostname:object:counter:instance:special options # # object is the top-level class such as "Processor" or "Memory". This is a # required entry. # counter is the "% Processor Time" or "% Committed Bytes in Use". This is # also a required entry. # instance is optional, but many counters have instances such as "_Total" # duration is optional, it defaults to 1 second. For many perfmon counters, # there is a required duration to get a time-based calculation. # special options is optional. Currently it accepts 'noscale' which turns # off scaling if perfmon so demands it. use Common::Log; use Common::Util; use Win32::PerfLib; use strict; $main::gDSFetch{'perfmon'} = \&perfmonFetch; sub perfmonFetch { my($dsList, $name, $target) = @_; my %pmonFetches; my $counter = {}; my $rcounter = {}; my(@results); foreach my $line (@{$dsList}) { my @components = split(/:/, $line); my ($index, $server, $myobject, $mycounter, $myinstance,$myduration,$myoptions); if($#components+1 < 3) { Error("Malformed datasource line: $line."); return(); } $index = shift(@components); $server = shift(@components) || missing("host",$line); $myobject = shift(@components) || 'System'; $mycounter = shift(@components) || 'System Up Time'; $myinstance = shift(@components) || ''; $myoptions = shift(@components) || ''; $pmonFetches{$index} = "$server:$myobject:$mycounter:$myinstance:$myoptions"; } my($perflib); my $count=0; my $num_fetch=keys %pmonFetches; DSLOOP: while(my ($index, $ilRef) = each %pmonFetches) { my $pr1 = {}; my $critical; my ($matches,$noscale) = int(0); my $value; my $fractop; my ($perfTimeOnly,$perfFreqOnly); my($server, $myobject, $mycounter, $myinstance, $myoptions) = split(/:/, $ilRef); $myobject =~ s/=/:/g; my @options = split(/,/, $myoptions); foreach my $op (@options) { if($op =~ /^noscale$/i) { $noscale = 1; } if($op =~ /^perftime$/i) { $perfTimeOnly = 'normal'; } if($op =~ /^perftime100ns$/i) { $perfTimeOnly = '100ns'; } if($op =~ /^perffreq$/i) { $perfFreqOnly = 1; } if($op =~ /^base$/i) { $fractop = 'base'; } if($op =~ /^fraction$/i) { $fractop = 'fraction'; } } if(!($counter->{$server})) { my $tempref = {}; Win32::PerfLib::GetCounterNames($server, $tempref) or $critical = $!; $counter->{$server} = $tempref; } if( (!$counter->{$server}) || $counter->{$server} == 0) { Error("No counters were retrieved from $server! Not wasting any more time on this ds: $ilRef"); push @results, "$index:U"; next DSLOOP; } # Get a list of mappings from CounterName -> id if (! $rcounter->{$server}) { foreach my $k (sort keys %{$counter->{$server}}) { $rcounter->{$server}->{lc($counter->{$server}->{$k})} = $k; } } $myobject = lc($myobject); $perflib = new Win32::PerfLib($server) if ($count++ == 0); #if(!($perflib)) { return; } $perflib->GetObjectList($rcounter->{$server}->{$myobject}, $pr1); $perflib->Close() if ($count >= $num_fetch); if($perfTimeOnly) { if($perfTimeOnly eq 'normal') { Debug("perfTimeOnly is $perfTimeOnly $pr1->{'PerfTime'}"); my $value = Common::Util::fixNum($pr1->{'PerfTime'}); push @results, "$index:$value"; next DSLOOP; } elsif ($perfTimeOnly eq '100ns') { Debug("perfTimeOnly is $perfTimeOnly $pr1->{'PerfTime100nSec'}"); my $value = Common::Util::fixNum($pr1->{'PerfTime100nSec'}); push @results, "$index:$value"; next DSLOOP; } } if($perfFreqOnly) { Debug("perfFreqOnly is $perfFreqOnly $pr1->{'PerfFreq'}"); my $value = sprintf("%0.20g", $pr1->{'PerfFreq'}); push @results, "$index:$value"; next DSLOOP; } foreach my $level2 (sort keys %{$pr1->{'Objects'}}) { my $gDenn = $pr1->{'PerfTime100nSec'}; my $gDen = $pr1->{'PerfTime'}; my $gTb = $pr1->{'PerfFreq'}; my $oldvalue; if($pr1->{'Objects'}->{$level2}->{'NumInstances'} > 0) { foreach my $level4 (sort keys %{$pr1->{'Objects'}->{$level2}->{'Instances'}}) { my $oldvalue; my $ir1 = $pr1->{'Objects'}->{$level2}->{'Instances'}->{$level4}; foreach my $level5 (sort keys %{$ir1->{'Counters'}}) { my $cr1 = $ir1->{'Counters'}->{$level5}; my $saneCounterName = $counter->{$server}->{$cr1->{'CounterNameTitleIndex'}}; my $saneInstanceName = $ir1->{'Name'}; if(lc($saneCounterName) eq lc($mycounter)) { if(lc($saneInstanceName) eq lc($myinstance) || !defined $myinstance) { $value = getCounter($cr1,$index,$ilRef,$noscale,$fractop); if($value ne "skip") { $oldvalue = $value; $matches++; } else { $value = $oldvalue; } } } } } } foreach my $level4 (sort keys %{$pr1->{'Objects'}->{$level2}->{'Counters'}}) { my $cr1 = $pr1->{'Objects'}->{$level2}->{'Counters'}->{$level4}; my $saneCounterName = $counter->{$server}->{$cr1->{'CounterNameTitleIndex'}}; if(lc($saneCounterName) eq lc($mycounter)) { $value = getCounter($cr1,$index,$ilRef,$noscale,$fractop); if($value ne "skip") { $oldvalue = $value; $matches++; } else { $value = $oldvalue; } } } } if($matches > 1) { Debug("More than one matches for datasource line: $ilRef. Only last instance will be used!"); } if($matches < 1) { Error("No matches for datasource line: $ilRef"); push @results, "$index:U"; } else { push @results, "$index:$value"; } } return @results; } sub getCounter { my ($cr1,$index,$ilRef,$noscale,$fractop) = @_; my ($crap,$junk,$cn) = split(/:/, $ilRef); my $scaler = $cr1->{'DefaultScale'}; my $ctype = Win32::PerfLib::GetCounterType($cr1->{'CounterType'}); if(defined($fractop)) { return "skip" if($fractop eq "base" && $ctype =~ /FRACTION/); return "skip" if($fractop eq "fraction" && $ctype =~ /BASE/); } Debug("ds$index COUNTER_TYPE is $ctype"); if(defined $fractop) { Debug("FractOp is set to: $fractop"); } my $value = $cr1->{'Counter'}; if(defined($noscale) && $noscale < 1) { $value = $value * (10**$scaler); } $value = Common::Util::fixNum($value); return $value; } sub missing { my ($missing,$line) = @_; Error("Missing perfmon $missing in datasource: $line"); return(); } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/lib/snmp.pm0100644000114300000000000000766307365656143014223 0ustar bertdwheel# -*-perl-*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # This lets people replace our choice of snmp libraries by simply # replacing snmpUtils.pm (Ed, this means you! ) use snmpUtils; use Common::Log; use strict; # This tells the collector that we are open for business and # happy to handle snmp:// datasources. $main::gDSFetch{'snmp'} = \&snmpFetch; sub snmpFetch { # This procedure is passed a REFERENCE to an array of SNMP datasources. # # Each element consists of a string like: # index://community@host:port/oid # community and port are optional. If they are left out, they # will default to public and 161, respectively. # # VERY IMPORTANT: The index MUST be returned with the corresponding value, # otherwise it'll get put back into the wrong spot in the RRD. my($dsList, $name, $target) = @_; my(%oidsPerSnmp) = (); my($dsspec); my($oidMap) = $main::gCT->configHash($name, 'oid'); foreach $dsspec (@{ $dsList }) { my($index); ($index, $dsspec) = split(/:/, $dsspec, 2); $dsspec =~ s#^//##; # This little hack is for people who like to use slashes in their # community strings. my($comm, $cinfo) = split(/@/, $dsspec, 2); my($snmp, $oid) = split(/\//, $cinfo, 2); $snmp = $comm . "@" . $snmp; $oid = mapOid($oidMap, $oid); if (! defined $oid) { Warn("Could not find an OID in $dsspec"); return (); } # Debug("SNMP parser: snmp://$snmp/$oid"); # we only try to process them if we didn't find a problem # above (hence the check for undef) push(@{ $oidsPerSnmp{$snmp} }, "$index:$oid") if (defined $oid); } my(@results) = (); while (my($snmp, $snmpDSRef) = each %oidsPerSnmp) { my(@oidsToQuery) = (); if ($#{ $snmpDSRef } >= 0) { my(@oidsToQuery) = my(@indices) = (); my($line); while ($line = shift @{ $snmpDSRef }) { my($index, $oid) = split(/:/, $line); push(@indices, $index); push(@oidsToQuery, $oid); } Debug("Getting from $snmp ", join(" ", @oidsToQuery)); my(@hostResults) = snmpUtils::get($snmp, @oidsToQuery); Debug("Got: ", join(" ", @hostResults)); # it tells us everything went to hell by returning # scalar -1. Unfortunately, we interpret that as the first # result. So, make it undef so that we fix it later. # hopefully, we don't need to fetch a -1 all the time... if (defined($hostResults[0]) && $hostResults[0] == -1) { $hostResults[0] = undef; } # turn undefs into "U"'s to make RRD happy my($ctr); for $ctr ( 0..$#indices ) { my($res) = $hostResults[$ctr]; $res = "U" if (! defined($res)); push(@results, "$indices[$ctr]:" . Common::Util::fixNum($res)); } } } return @results; } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/lib/snmpUtils.pm0100644000114300000000000000632410017671051015214 0ustar bertdwheel# This is a simple wrapper for SNMP_utils. People who want to # use other SNMP libraries can hook the calls here by replacing this # copy of snmpUtils.pm with their own, which redirects the calls # to their own library. package snmpUtils; use Common::Log; use BER; use SNMP_Session; use SNMP_util; use Sys::Hostname; # snmp_utils 0.55 has a reference to main::DEBUG, so we instantiate # it here, if necessary if (! defined($main::DEBUG)) { $main::DEBUG = 0; } # this keeps BER from formatting timeticks, so that if someone # want to put them into an RRD, it would work. $BER::pretty_print_timeticks = 0; my($err) = ''; # Max number of times a device can fail to respond before we skip further # requests. Adjust as needed. (This should probably be made a target # attribute in the config tree so it can be set on a per device basis.) my $MAXTRIES = 2; my %skipcnt; sub init { %skipcnt = (); } # this funky wrapper is because SNMP_Session throws exceptions with # warn(), which we need to catch, instead of letting them run # roughshod over Cricket's output. sub _do { my($subref, @args) = @_; my(@res); return (-1) if (defined $skipcnt{$args[0]} && $skipcnt{$args[0]} >= $MAXTRIES); $err = ''; eval { local($SIG{'__WARN__'}) = sub { $err = $_[0]; die($err); }; @res = &{$subref}(@args); }; # do a little post processing on the overly wordy errors # that SNMP_Session gives us... if (defined($err) && $err ne '') { my(@err) = split(/\n\s*/, $err); if ($err[1] eq "no response received") { $skipcnt{$args[0]}++; my $host = (split(/: /,$err[2]))[1]; $host =~ s/\)$//; $err = "No response from $host"; $err .= " - Skipping." if ($skipcnt{$args[0]} >= $MAXTRIES); } elsif ($#err+1 > 2) { my($code) = (split(/: /, $err[2]))[1]; $code = ": undefined error code" if (!$code); $err = "$err[1] $code."; if ($code eq "noSuchName") { my($oid) = $err[3]; $oid =~ s/.*\((.*)\).*/$1/; $err .= " $oid"; } } else { $err =~ s/\n//g; } Warn($err); } return @res; } sub get { _do(\&snmpget, @_); } sub getnext { _do(\&snmpgetnext, @_); } sub walk { _do(\&snmpwalk, @_); } sub trap2 { my($to, $spec, @data) = @_; # this is the OID for enterprises.webtv.wtvOps.wtvOpsTraps my($ent) = ".1.3.6.1.4.1.2595.1.1"; _do(\&snmptrap, $to, $ent, hostname(), 6, $spec, @data); } sub trap { my($to, $spec, @data) = @_; # this is the OID for enterprises.webtv.wtvOps.wtvOpsTraps my($ent) = ".1.3.6.1.4.1.2595.1.1"; # this makes a oid->value map for the trap. Note that # we just fake up simple one-level OID's... it suits our needs. my($item, @vars); my($ct) = 1; foreach $item (@data) { my($type) = "string"; $type = "integer" if ($item =~ /^(\d+)$/); push @vars, $ct, $type, $item; $ct++; } _do(\&snmptrap, $to, $ent, hostname(), 6, $spec, @vars); } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/lib/sql.pm0100755000114300000000000000520307737663612014036 0ustar bertdwheel# -*-perl-*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. use Common::Log; use Common::Util; use DBI; use sqlUtils; use strict; $main::gDSFetch{'sql'} = \&sqlFetch; sub sqlFetch { my($dsList, $name, $target) = @_; my @results; my %sqlFetches; foreach my $line (@{$dsList}) { my @components = split(/:/, $line, 6); my ($index, $login, $password, $query, $col, $dbdriver); if($#components+1 < 6) { Error("Malformed datasource line: $line."); return(); } $index = shift(@components); $login = shift(@components) || 'anonymous'; $password = shift(@components) || ''; $query = shift(@components) || missing("sql query", $line); $col = shift(@components) || 1; $dbdriver = shift(@components) || missing("db driver", $line); $sqlFetches{$index} = "$login:$password:$query:$col:$dbdriver"; } DSLOOP: while(my ($index, $ilRef) = each %sqlFetches) { my($login, $password, $query, $col, $dbdriver) = split(/:/, $ilRef, 5); my $matches; my $value; my $dbh = DBI->connect($dbdriver, $login, $password) || Error(); my $sth = $dbh->prepare($query); if($sth->errstr) { Error "Bad query: $sth->errstr"; } $sth->execute; if($sth->errstr) { Error "Bad result: $sth->errstr"; } my @row = $sth->fetchrow_array(); $value = $row[$col-1]; $matches++; if($sth->fetchrow_array()) { $matches++; } if($matches < 1) { push @results, "$index:U"; } else { push @results, "$index:$value"; } } return @results; } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/lib/sqlUtils.pm0100644000114300000000000000146507371603553015052 0ustar bertdwheelpackage sqlUtils; use Common::Log; sub sendto { my ($args, @data) = @_; my ($driver, $login, $password, $sybhome) = split(/,/, $args); my $dbh = DBI->connect($driver, $login, $password) || Error("DBH connect failed because of: $DBI::errstr"); my $ds = 0; foreach my $val (@data) { my @time = localtime(); my $timestamp = ($time[4] + 1) . "-" . ($time[3]) . "-" . ($time[5] + 1900) . " $time[2]:$time[1]"; $dbh->do("use cricket; " . "insert into CricketData (targetPath, targetName, ds, " . "value, timestamp) " . "values ('$tpath', '$tname', '$ds', '$val', '$timestamp')"); $ds++; } } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/lib/wbem.pm0100755000114300000000000001351107547113603014157 0ustar bertdwheel# -*-perl-*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # A fetch routine for retrieving data from Windows Management # Instrumentation Counters, the Microsoft implementation of the Web-Based # Enterprise Management (WBEM) standard. This implementation feeds WQL # (similar to SQL) queries to the WMI COM Interface. use Common::Log; use Win32::OLE; use Win32::OLE::Enum; # V.* reg exp should match verion 1.1 (Win2K) or 1.2 (WinXP) use Win32::OLE::Const 'Microsoft WMI Scripting V.* Library'; $main::gDSFetch{'wbem'} = \&wbemFetch; sub wbemFetch { # This procedure is passed a REFERENCE to an array of wbem datasources. # Each line consists of "index:host:namespace:table:field:predicate" # Predicate is optional. # VERY IMPORTANT: The index MUST be returned with the corresponding value, # otherwise it'll get put back into the wrong spot in the RRD. my($dsList, $name, $target) = @_; my (@results, @lines, $line, @nextline); my ($locator, $service, $objectSet, $object) = (undef, undef, undef, undef); my $query = undef; my ($index, $host, $namespace, $table, $field, $predicate); my (@fieldList, @indexList); my $isConnected = 0; # sort lines by host, namespace, and table # (default they are sorted by index) @lines = sort { substr ($a, index($a, ':')) cmp substr ($b, index($b, ':')); } (@{$dsList}); # connect or load the WMI locator $locator = Win32::OLE->GetActiveObject("WbemScripting.SWbemLocator"); $locator = Win32::OLE->new("WbemScripting.SWbemLocator") unless defined $locator; Error("failed to load WMI locator") unless defined $locator; $locator -> {Security_} -> {ImpersonationLevel} = wbemImpersonationLevelImpersonate; $line = shift @lines; CONNECTLOOP: while (defined($line)) { ($index, $host, $namespace, $table, $field, $predicate) = split(/:/,$line,6); # obtain the WMI service interface for the namespace/host undef $service; $service = $locator -> ConnectServer($host,$namespace); if (defined $service) { $isConnected = 1; } else { Warn("ConnectServer to $host $namespace failed:"); Warn(Win32::OLE->LastError()); $isConnected = 0; } TABLELOOP: while (1) { @fieldList = (); @indexList = (); FIELDLOOP: while (1) { push @fieldList, "$field"; push @indexList, $index; # group together queries to the same table with the same predicate $line = shift @lines; @nextline = split(/:/,$line,6) if (defined($line)); if (!defined($line) || $table ne $nextline[3] || ($predicate ? $predicate : "") ne ($nextline[5] ? $nextline[5] : "") || $host ne $nextline[1] || $namespace ne $nextline[2]) { last FIELDLOOP; } ($index, $field) = ($nextline[0], $nextline[4]); } # construct and run the query $query = "SELECT " . join(',',@fieldList) . " FROM $table"; $query .= " WHERE $predicate" if ($predicate); undef $objectSet; $objectSet = $service -> ExecQuery($query, "WQL", wbemFlagReturnWhenComplete) if ($isConnected); if (!($isConnected) || !$objectSet) { Warn("Query {$query} against $host $namespace failed:"); Warn(Win32::OLE->LastError()) if $isConnected; Warn("no connection") unless $isConnected; push @results, map { $_ . ':U' } @indexList; } else { $object = undef; my @temp = in $objectSet; # query should always be singleton select $object = shift @temp; if ($object) { for (my $i = 1; $i <= scalar(@fieldList); $i++) { my $data = $object -> {$fieldList[$i-1]}; push @results, "$indexList[$i - 1]:$data" } } else { Warn("No result from $query: " . Win32::OLE->LastError()); push @results, map { $_ . ':U' } @indexList; } } # @nextline is already set # check if a new connection is required # use an order designed to take advantage of shortcut evaluation if (!defined($line) || $host ne $nextline[1] || $namespace ne $nextline[2]) { last TABLELOOP; } # continue with the next table/predicate ($index, $host, $namespace, $table, $field, $predicate) = @nextline; } } # not certain is this required undef $object; undef $objectSet; undef $service; undef $locator; return @results; } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/lib/Common/0040755000114300000000000000000010031605175014106 5ustar bertdwheelcricket-1.0.5/lib/Common/HandleTarget.pm0100644000114300000000000002204007771714042017014 0ustar bertdwheel# -*- perl -*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. package Common::HandleTarget; require Exporter; @ISA = qw(Exporter); @EXPORT = qw(handleTarget handleTargetInstance checkTargetInstance); use Common::Log; use Common::Util; use Common::Map; use RRD::File; sub handleTarget { my($name, $instHandler, $cb) = @_; Debug("Processing $name..."); my($target) = $Common::global::gCT->configHash($name, 'target'); ConfigTree::Cache::addAutoVariables($name, $target, $Common::global::gConfigRoot); my($tname) = $target->{'auto-target-name'}; Common::Map::mapPrepareInstance($target); # check for non-scalar instance # and handle as a special case with a loop my(@inst); if (defined($target->{'inst'})) { my($inst) = $target->{'inst'}; $inst = ConfigTree::Cache::expandString($inst, $target, \&Warn) if (length($inst) > 2 && index($inst, "%") >= 0); Debug("Evaling inst which is: $inst"); $inst = quoteString($inst); @inst = Eval($inst); } else { @inst = (); } if ($#inst+1 > 1) { my $instnames; my(@instnames) = (); my $hasInstNames = 0; if (defined($target->{'inst-names'})) { $instnames = $target->{'inst-names'}; $instnames = ConfigTree::Cache::expandString($instnames,$target,\&Warn); $instnames = quoteString($instnames); @instnames = Eval($instnames); $hasInstNames = 1; } my($inst); foreach $inst (@inst) { $instnames = shift @instnames if $hasInstNames; # copy the current target to a temp target we can play # with -- i.e. set the inst to a scalar, then expand my($k, $v, $tmpTarg); $tmpTarg = {}; foreach $k (keys(%{$target})) { $tmpTarg->{$k} = $target->{$k}; } $tmpTarg->{'inst'} = $inst; # use inst-name instead of inst-names here because # I have no idea if inst-names is used later in the code $tmpTarg->{'inst-name'} = $instnames if $hasInstNames; Debug("Processing target $tname, instance $inst."); &{$instHandler}($name, $tmpTarg, $cb); } } else { # this is a scalar case, just continue into handleTargetInstance. $target->{'inst'} = (defined($inst[0]) ? $inst[0] : ""); &{$instHandler}($name, $target, $cb); } } sub handleTargetInstance { my($name, $target, $cb) = @_; # expand it wrt itself ConfigTree::Cache::expandHash($target, $target, \&Warn); # this will do instance mapping, or leave the target untouched # if there's no work to do. (mapPrepareInstance has to have been # called first...) Common::Map::mapInstance($name, $target); # now, call the callback passed into us by the # parent &{$cb}($name, $target); } sub checkTargetInstance { my($name,$target) = @_; # expand it wrt itself ConfigTree::Cache::expandHash($target, $target, \&Warn); if (! $target->{'monitor-thresholds'}) { return; } my($m) = new Monitor; my($rrd) = new RRD::File( -file=>$target->{'rrd-datafile'} ); if (!$rrd->open()) { Info("Couldn't open RRD File for $name, skipping this target."); return; } my $rrdpollinterval = $target->{'rrd-poll-interval'}; my $time = time(); # Convert backslashed commas to \0's ; my $ThresholdString = $target->{'monitor-thresholds'}; $ThresholdString =~ s/\\,/\0/g ; my(@ThresholdStrings) = split(/\s*,\s*/, $ThresholdString ); my($Threshold); # General monitor threshold format: # datasource:monitor type:monitor args:action:action args # Current supported actions: FUNC, EXEC, FILE, MAIL, SNMP, META # Action args are colon (:) delimited. # Monitor args are colon (:) delimited, but must not collide with # any of the action tags. # SNMP is the exception; it has no action args and instead uses # the target variable trap-address. This exception is maintained for # backwards compatibility. # META only stores an alarm in the *.meta files; It can have any number of # action arguments which can serve to group alarms # for classification or to identify a priority priority level. Arguments # are not interpreted or processed in any way by Monitor.pm. foreach $Threshold (@ThresholdStrings) { # restore escaped commas $Threshold =~ s/\0/,/g ; my($span) = 0; my($spanlength); my($ds,$type,$args) = split(/\s*:\s*/, $Threshold, 3); ($args, $spanlength) = split(/:SPAN:/, $args, 2) if ($args =~ /SPAN/); if (defined ($spanlength) && $spanlength =~ /\d+/ ){ $span = 1; } # hide escaped colons $args =~ s/\\:/\0/g ; # default action type is SNMP my($actionType) = 'SNMP' ; my(@actionArgs); # search for an action tag if ( $args =~ /^(.*)\s*:\s*(FUNC|EXEC|FILE|MAIL|META)\s*:\s*(.*)$/ ) { $args = $1 ; $actionType = $2 ; # restore escaped colons in the monitor args field $args =~ s/\0/:/g ; my $action_args = $3; # action args are colon-delimited @actionArgs = split(/\s*:\s*/, $action_args); # restore escaped colons in the action args field map { $_ =~ s/\0/:/g } @actionArgs; } elsif ( $args =~ /^(.*)\s*:\s*(SNMP|META)\s*$/ ) { $args = $1 ; $actionType = $2 ; # restore escaped colons $args =~ s/\0/:/g ; } if (defined($Common::global::gMonitorTable{"$type"})) { my $persistent = $target->{'persistent-alarms'}; $persistent = 'false' if (!defined($persistent)); my ($rc, $val) = &{$Common::global::gMonitorTable{"$type"}} ($m, $target, $ds, $type, $args); if ($rc) { # the test succeeded, check to see if we # should send a trap or not LogMonitor("$name - $Threshold passed."); my($metaRef) = $rrd->getMeta(); if (defined($metaRef->{$Threshold})) { LogMonitor("Triggering recovery for $Threshold."); $m->Clear($target,$name,$ds,$type,$Threshold,$actionType, \@actionArgs,$val); delete($metaRef->{$Threshold}); $rrd->setMeta($metaRef); } } else { my($metaRef) = $rrd->getMeta(); my ($x, $mt, $mevent, $rest); ($x,$mt, $mevent, $rest) = split (/\s+/, $metaRef->{$Threshold},4) unless (!defined($metaRef->{$Threshold})); $mt = $time if !defined($mt); LogMonitor("$name - $mt - $Threshold failed."); my($spanfail) = 0; if ($span && $mt < ($time - ($spanlength * $rrdpollinterval) - 90)) { $spanfail = 1; } if ((!$span || $span && $spanfail) && ($persistent eq "true" || !defined($metaRef->{$Threshold})) ) { LogMonitor("Triggering alarm for $Threshold."); $m->Alarm($target,$name,$ds,$type,$Threshold,$actionType,\@actionArgs,$val); $metaRef->{$Threshold} = ' '. $mt .' failure value '. $val; $rrd->setMeta($metaRef); } elsif ($span && (!defined($mevent)) || (($mevent ne 'failure') && $spanfail)) { LogMonitor("Triggering a span event for $Threshold."); my($event) = ($spanfail) ? 'failure' : 'spanevent'; $m->Alarm($target,$name,$ds,$type,$Threshold,$actionType,\@actionArgs,$val) if ($spanfail); $metaRef->{$Threshold} = ' '. $mt . ' ' . $event . ' value '. $val; $rrd->setMeta($metaRef); } } } else { Warn("No monitor handler defined for monitor type $type"); } } $rrd->close(); } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/lib/Common/Log.pm0100644000114300000000000001036507750556345015211 0ustar bertdwheel# -*- perl -*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. package Common::Log; require Exporter; @ISA = qw(Exporter); @EXPORT = qw(Debug Warn Info Die Error LogMonitor isDebug); $kLogDebug = 9; $kLogMonitor = 8; $kLogInfo = 7; $kLogWarn = 5; $kLogError = 1; $gCurLogLevel = $kLogWarn; %kLogNameMap = ( 'debug' => $kLogDebug, 'monitor' => $kLogMonitor, 'info' => $kLogInfo, 'warn' => $kLogWarn, 'error' => $kLogError ); %kLogNameReverseMap = ( $kLogDebug => 'debug', $kLogMonitor => 'monitor', $kLogInfo => 'info', $kLogWarn => 'warn', $kLogError => 'error' ); $kLogMinimal = 1; $kLogStandard = 2; $kLogExtended = 3; $gCurLogFormat = $kLogStandard; %kLogFormatMap = ( 'minimal' => $kLogMinimal, 'standard' => $kLogStandard, 'extended' => $kLogExtended ); sub Log { my($level, @msg) = @_; my($msg) = join('', @msg); return unless ($level <= $gCurLogLevel); my($severity) = ' '; $severity = '*' if (($level == $kLogWarn) || ($level == $kLogError)); if ($gCurLogFormat == 2) { my($stuff) = timeStr(time()) . $severity; if(defined($main::th)) { print STDERR "[$stuff] ($main::th) $msg\n"; } else { print STDERR "[$stuff] $msg\n"; } } elsif ($gCurLogFormat == 3) { my($stuff) = timeStr(time()) . $severity; my($levelname) = ucfirst($kLogNameReverseMap{$level}); printf STDERR ("[$stuff] %-5s $msg\n", $levelname); } else { my($levelname) = ucfirst($kLogNameReverseMap{$level}); printf STDERR ("[%-5s%1s] $msg\n", $levelname, $severity); } } sub Die { Log($kLogError, @_); die("Exiting due to unrecoverable error.\n"); } sub Error { Log($kLogError, @_); } sub Warn { Log($kLogWarn, @_); } sub Debug { Log($kLogDebug, @_); } sub Info { Log($kLogInfo, @_); } sub LogMonitor { Log($kLogMonitor, @_); } sub isDebug { return 1 if $gCurLogLevel >= $kLogDebug; return 0; } sub timeStr { my($t) = ($_[0] =~ /(\d*)/); my(@months) = ( "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); my($sec,$min,$hour,$mday,$mon,$year) = localtime($t); return sprintf("%02d-%s-%04d %02d:%02d:%02d", $mday, $months[$mon], $year + 1900, $hour, $min, $sec); } sub setLevel { my($level) = @_; my($ilevel) = $gCurLogLevel; if (defined($kLogNameMap{lc($level)})) { $gCurLogLevel = $kLogNameMap{lc($level)}; Common::Log::Info("Log level changed from ", $kLogNameReverseMap{$ilevel}, " to $level."); } else { Common::Log::Warn("Log level name $level unknown. " . "Defaulting to 'info'."); $gCurLogLevel = $kLogNameMap{lc('info')}; } } sub setFormat { my($format) = @_; if (defined($kLogFormatMap{lc($format)})) { $gCurLogFormat = $kLogFormatMap{lc($format)}; } else { Common::Log::Warn("Log format name $format unknown. " . "Defaulting to 'standard'."); $gCurLogFormat = $kLogFormatMap{lc('standard')}; } } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/lib/Common/Map.pm0100644000114300000000000001444707365660517015211 0ustar bertdwheel# -*- perl -*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. package Common::Map; use snmpUtils; use Common::Log; use Common::Util; # This sets up the target right to sneak through the instance # evaluation code... we do the actual mapping after the target # gets expanded. sub mapPrepareInstance { my($target) = @_; my($inst) = $target->{'inst'}; return unless defined($inst); Debug("Preparing $inst"); my($mapkey) = ($inst =~ /^map\((.*)\)$/); return unless defined($mapkey); # ok, if we got this far, we have a mapkey, so put it into # the target dict for later, and fake the inst into an # eval-able thing so that we can squeak through the instance # eval code. $target->{'--mapkey--'} = lc($mapkey); $target->{'inst'} = "0"; return; } sub mapInstance { my($name, $target) = @_; # Don't try to map it if we should be touching it: return if defined($target->{'targets'}); return if defined($target->{'mtargets'}); if ($Common::global::isCollector) { # we only honor collect=false in the collector, since # we still need to map it in the grapher so that we # have the right instance number on hand in the grapher. return if (defined($target->{'collect'}) && isFalse($target->{'collect'})); } my($mapkey) = $target->{'--mapkey--'}; # there's no work to do, so leave. return unless (defined($mapkey)); # if there's no file for some reason, all this fails # correctly -- i.e. no meta data is returned. my($rrd, $file); $file = $target->{'rrd-datafile'}; $rrd = new RRD::File ( -file => $file ); # we only want to check the cache if we are coming # through here for the first time... see retrieveData # the the "second coming" of mapInstance. my($metaRef) = $rrd->getMeta(); if (defined($target->{'--verify-mapkey--'})) { # Lose the cached last-inst to force the SNMP walk, # since evidently the cached one didn't work. delete($metaRef->{'last-inst'}); } # if cached, try to use it, but warn the retriever it needs to # verify the inst. if not cached, do a lookup. my($cachedInst) = $metaRef->{'last-inst'}; if (defined($cachedInst)) { $target->{'inst'} = $cachedInst; $target->{'--verify-mapkey--'} = $mapkey; } else { my($inst) = mapLookup($name, $target); if (! defined($inst)) { # set the inst key to null so that our target ends up # unresolveable -- i.e. the OID will come out useless $target->{'inst'} = ''; Warn("Failed to map instance for " . $target->{'auto-target-name'} . ". Instance is now set to nothing."); } else { $target->{'inst'} = $inst; # save it for next time $metaRef->{'last-inst'} = $inst; $rrd->setMeta($metaRef); } } } # lookup does the hard work. mapkey tells us which map entry # to use. then we use the ds and match tags in that entry # to find the instance number sub mapLookup { my($name, $target) = @_; my($mapkey) = $target->{'--mapkey--'}; return unless defined($mapkey); my($mapRef) = $Common::global::gCT->configHash($name, 'map', $mapkey, $target); my($match) = $mapRef->{'match'}; if (! defined($match)) { Warn("No match tag found in map entry $mapkey"); return; } $match = ConfigTree::Cache::expandString($match, $target, \&Warn); # this is expected to hold a string like this: comm@host:port my($snmp) = $target->{'snmp'}; if (! defined($main::gMapCache{$snmp}->{$mapkey})) { # cache does not exist, so try to load it my($baseOID) = $mapRef->{'base-oid'}; my($oidMap) = $Common::global::gCT->configHash($name, 'oid'); my($oid) = Common::Util::mapOid($oidMap, $baseOID); if (! defined($oid)) { Warn("Missing base-oid in $mapkey map entry."); return; } my($hp) = $snmp; $hp =~ s/^.*@//; # remove the community string, if it's there # (This keeps it out of the logfiles, which seems like # a good idea.) Info("Walking $baseOID for $hp to resolve $mapkey mapping"); my(@ret) = snmpUtils::walk($snmp, $oid); my($row); foreach $row (@ret) { my($inst, $name) = split(':', $row, 2); $main::gMapCache{$snmp}->{$mapkey}->{$name} = $inst; } } # find the inst number -- either by using a regexp match, # or by a simple table lookup. # does it look like a regexp? (i.e. "/^foo$/") (we allow # whitespace, in case they are incompetent with the quote key... if ($match =~ /^\s*\/(.*)\/\s*$/) { # this is a regexp $match = $1; Debug("Regexp is /$match/i"); # this resets the iterator, so the each will get everything scalar keys(%{$main::gMapCache{$snmp}->{$mapkey}}); my($name, $inst); while (($name, $inst) = each(%{$main::gMapCache{$snmp}->{$mapkey}})) { Debug(" checking: $name"); if ($name =~ /$match/i) { return $inst; } } # didn't match anything... return nothing. return; } else { Debug("Attempting lookup on $match."); return $main::gMapCache{$snmp}->{$mapkey}->{$match}; } } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/lib/Common/Options.pm0100644000114300000000000000352507750320761016112 0ustar bertdwheel# -*- perl -*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. package Common::Options; use Getopt::Long; use Common::Log; sub commonOptions { # default to 'info' unless there's a environment variable # or a commandline arg my($logLevel, $logFormat, $base); $logLevel = $Common::global::gLogLevel if $Common::global::gLogLevel; $logLevel = $ENV{'CRICKET_LOG_LEVEL'} if $ENV{'CRICKET_LOG_LEVEL'}; $logLevel ||= "info"; # default to 'standard' unless there's a environment variable $logFormat = $Common::global::gLogFormat if $Common::global::gLogFormat; $logFormat = $ENV{'CRICKET_LOG_FORMAT'} if $ENV{'CRICKET_LOG_FORMAT'}; $logFormat ||= "standard"; GetOptions( "loglevel:s" => \$logLevel, "logformat:s" => \$logFormat, "base:s" => \$base, @_); $Common::global::gConfigRoot = $base if $base; Common::Log::setLevel($logLevel); Common::Log::setFormat($logFormat); } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/lib/Common/Util.pm0100644000114300000000000001071507777360311015377 0ustar bertdwheel# -*- perl -*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. package Common::Util; require Exporter; @ISA = qw(Exporter); @EXPORT = qw(Eval isNonNull isTrue isFalse isNaN mapOid runTime quoteString mergeHash); use Common::Log; use Digest::MD5; # This is used to help not-strictly-numeric instances sneak through # the eval. This is a hack and should probably be solved better # someday. sub quoteInstance { my($targRef) = @_; my($inst) = $targRef->{'inst'}; if (defined($inst)) { $targRef->{'inst'} = quoteString($targRef->{'inst'}); } } sub quoteString { my($str) = @_; if ($str =~ /^[A-Za-z]/) { if ($str !~ /^qw\(/) { $str = "qw($str)"; } } return $str; } # sub quoteString { # my($str) = @_; # my($new) = $str; # # if ($str =~ /^[\w\.-]+$/) { # $new = "\'$str\'"; # Debug("Quoted string [$str] to be [$new]"); # } # return $new; #} sub mapOid { my($oidRef, $oid) = @_; return $oid unless $oid; while (($oidName) = ($oid =~ /([a-z]\w*)/i)) { if (defined $oidRef->{lc($oidName)}) { $oid =~ s/$oidName/$oidRef->{lc($oidName)}/; } else { Warn("Could not resolve OID for $oid"); $oid = undef; } } return $oid; } # os-independent MkDir sub MkDir { my($dir) = @_; if (isWin32()) { my ($dd) = $dir; $dd =~ s/\//\\/g; `cmd /X /C mkdir $dd`; } else { system("/bin/mkdir -p $dir"); } } sub isWin32 { return ($^O eq 'MSWin32'); } sub runTime { my $starttime = shift; $starttime = $^T unless defined $starttime; my($time) = time() - $starttime; if ($time > 59) { my($min) = int($time / 60); my($sec) = $time - ($min * 60); $time = "$min minutes, $sec"; } $time .= " seconds"; return $time; } # Perl doesn't like numbers in scientific notation. It makes it very unhappy. # Thanks to Steen Linden for helping with a fix for this. sub fixNum { my($n) = @_; if (!defined($n)) { Error("Value not defined in Common::Util::fixNum()!"); } $n = sprintf("%0.20g", $n) if ($n =~ /^\d\.\d+e\+\d+$/); return $n; } sub isFalse { my($v) = @_; $v = lc($v) if (defined($v)); if ($v eq '0' or $v eq 'false' or $v eq 'no') { return 1; } return 0; } sub isTrue { return ! isFalse(@_); } sub isNaN { return ($_[0] =~ /^NaN/i); } sub Eval { my($exp) = @_; my(@res); my($warn); my($p, $f, $l) = caller(); eval { local($SIG{'__WARN__'}) = sub { $warn = $_[0]; die($warn); }; #Debug("evaling ($f, line $l): $exp"); @res = eval($exp); }; if (defined($warn)) { Warn("Warning while evaluating $exp: $warn"); Debug("Called from $f, line $l."); } return @res; } sub isNonNull { return (defined($_[0]) && $_[0] ne ''); } # add elements from hashRef2 to hashRef1 # bOverride indicate that hashRef2 elements with the same key # should override the corresponding elements in hashRef1 sub mergeHash { my ($hashRef1, $hashRef2, $bOverride) = @_; my $curKey; foreach $curKey (keys %{$hashRef2}) { if (!(exists $hashRef1->{$curKey}) || $bOverride) { $hashRef1->{$curKey} = $hashRef2->{$curKey}; } } } # Munge an overly long datasource name to fit RRD's constraints sub mungeDsName { my $name = lc shift; return $name if length($name) <= 19; my $md5 = Digest::MD5::md5_base64($name); $md5 =~ tr/A-Z0-9//cd; return substr($name, 0, 15) . substr($md5, 0, 4); } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/lib/Common/Version.pm0100644000114300000000000000017510031605175016071 0ustar bertdwheel$Common::global::gVersion = "Cricket version 1.0.5 (2004-03-28)"; $Common::global::gVersion =~ s/[!]!VERSION![!]/devel/; 1; cricket-1.0.5/lib/Common/global.pm0100644000114300000000000000522407740443466015724 0ustar bertdwheel# -*- perl -*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # define the ConfigRoot class package ConfigRoot; my $val; sub TIESCALAR { my $class = shift; my $me; $val = shift; bless \$me, $class; } sub FETCH { my $self = shift; if (!defined($val)) { return $Common::global::gCricketHome . "/cricket-config"; # check for relative path (both UNIX and DOS drive letter style) } elsif ($val !~ m#^/# && $val !~ m#^[a-z,A-Z]:/#) { return "$Common::global::gCricketHome/$val" unless ($^O eq 'MSWin32' && $Common::global::isGrapher); } return $val; } # this method will only be invoked if someone sets $gConfigRoot # after Common::global is loaded sub STORE { my $self = shift; $val = shift; return $self->FETCH(); } package Common::global; BEGIN { # Set defaults for things not picked up from cricket-config.pl $gCricketHome ||= $ENV{'HOME'}; tie $gConfigRoot, 'ConfigRoot', $gConfigRoot; if ($^O eq 'MSWin32') { $gCacheDir ||= "$ENV{'TEMP'}\\cricket-cache" if (defined($ENV{'TEMP'})); $gCacheDir ||= "c:\\temp\\cricket-cache"; } else { $gCacheDir ||= "$ENV{'TMPDIR'}/cricket-cache" if (defined($ENV{'TMPDIR'})); $gCacheDir ||= "/tmp/cricket-cache"; } $hasPersistantGlobals ||= 0; $hasPersistantGlobals = 1 if $ENV{'MOD_PERL'}; $hasPersistantGlobals = 1 if $CGI::SpeedyCGI::i_am_speedy; $gSkipMonitor ||= 0; $gUrlStyle ||= "classic"; if (!defined($isGrapher)) { $isGrapher = 0; } if (!defined($isCollector)) { $isCollector = 0; } if (!defined($gLongDSName)) { $gLongDSName = 0; } if (!defined($gLogFullPath)) { $gLogFullPath = 0; } } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/lib/ConfigTree/0040755000114300000000000000000010031605165014702 5ustar bertdwheelcricket-1.0.5/lib/ConfigTree/Cache.pm0100644000114300000000000001737007770162637016272 0ustar bertdwheel# Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. package ConfigTree::Cache; use DB_File; use POSIX; sub DbRef { shift->_getAndSet('DbRef', @_) }; sub Dbh { shift->_getAndSet('Dbh', @_) }; sub Base { shift->_getAndSet('Base', @_) }; sub Warn { shift->_getAndSet('Warn', @_) }; sub _getAndSet { my($self, $field, $value) = @_; my($retval) = $self->{$field}; $self->{$field} = $value if ($#_ >= 2); return $retval; } sub new { my($class) = @_; my($self) = {}; $self->{"LastCompile"} = 0; bless $self, $class; return $self; } sub init { my($self) = @_; my($file) = $self->{"Base"} . "/config.db"; my ($dbh); my ($useSlurp) = 0; my($mtime) = (stat($file))[9]; if ($self->{"LastCompile"} == $mtime) { return $self->Dbh(); } $Common::global::gDbAccess ||= "slurp"; if (($Common::global::gDbAccess eq "slurp") && ($Common::global::isCollector == 1)) { $useSlurp = 1; } if ($useSlurp) { ($dbh) = tie %db2, 'DB_File', $file, O_RDONLY, 0644, $DB_BTREE; %db = %db2; } else { ($dbh) = tie %db, 'DB_File', $file, O_RDONLY, 0644, $DB_BTREE; } $self->DbRef(\%db); $self->Dbh($dbh); $self->{"LastCompile"} = $mtime if $dbh; return $dbh; } sub nodeExists { my($self, $node) = @_; return defined($self->{"DbRef"}->{'p:' . $node}); } sub visitLeafs { my($self, $parent, $cb, @args) = @_; my($dbref) = $self->{"DbRef"}; my($children) = $dbref->{'c:' . $parent}; if ($children) { my($child); foreach $child (split(/,/, $children)) { $self->visitLeafs($child, $cb, @args); } } else { if (! $self->isDir($parent)) { &{$cb}($parent, @args); } } return; } sub configHash { my($self, $node, $dict, $name, $exp) = @_; my($dbRef) = $self->{"DbRef"}; # if they ask for a part of the config tree that does not # exist, return an error immediately. if (! $self->nodeExists($node)) { return; } # walk up from the node in question, finding a path to # the root. Build up the list backwards so going thru it forwards # is a path from the root to the node of interest. my(@path, $curnode); $curnode = $node; while (length($curnode) > 0) { unshift @path, $curnode; last unless ($curnode =~ s/\/[^\/]+$//); } unshift @path, "/"; # now that we have a path from the root down, compile all the # data into a hash to hand back to the caller. my($hash) = {}; # one good special case deserves another. Sigh. if ($dict eq 'target') { ($name) = ($node =~ /^.*\/(.*)$/); } # when they give us no name, they are looking for one of the # goofy nameless dicts. if (! defined($name)) { $name = '--merril--'; } my($item); foreach $item (@path) { my($tags, $tag); # try once for --def-- $tags = $dbRef->{"t:$item:$dict:--default--"}; $tags = '' unless defined($tags); foreach $tag (split(/,/, $tags)) { $hash->{$tag} = $dbRef->{"d:$item:$dict:--default--:$tag"}; } # ...and try once for $name $tags = $dbRef->{"t:$item:$dict:$name"}; $tags = '' unless defined($tags); foreach $tag (split(/,/, $tags)) { $hash->{$tag} = $dbRef->{"d:$item:$dict:$name:$tag"}; } } # auto-expand, if the caller asked us to if (defined($exp)) { if (ref($exp) eq 'HASH') { expandHash($hash, $exp, $self->{"Warn"}); } else { # they want us to setup a target hash for them... addAutoVariables($node, $hash, $self->{"Base"}); expandHash($hash, $hash, $self->{"Warn"}); } } return $hash; } sub addAutoVariables { my($name, $target, $base) = @_; my($tpath, $tname) = ($name =~ /^(.*)\/(.*)$/); $target->{'auto-base'} = $base; $target->{'auto-target-path'} = $tpath; $target->{'auto-target-name'} = $tname; my($root) = $tpath; $root =~ s/([^\/]+)/../g; $target->{'auto-root'} = $root; return; } sub getChildren { my($self, $name) = @_; my($c) = $self->{"DbRef"}->{"c:$name"}; if (defined($c)) { return split(/,/, $c); } return (); } sub isDir { my($self, $name) = @_; if (defined($self->{"DbRef"}->{"r:$name"})) { return 1; } else { return 0; } } sub isLeaf { my($self, $name) = @_; my(@c) = $self->getChildren($name); return (($#c+1 == 0) && (! $self->isDir($name))); } sub needsRecompile { my($self) = @_; my($db) = $self->{"DbRef"}; my($files) = $db->{"F:"}; if (defined($files)) { my($file); foreach $file (split(/,/, $files)) { my($mtime) = (stat($file))[9]; if (defined($mtime)) { my($mtime2) = $db->{"f:$file"}; if (defined($mtime2)) { if ($mtime > $mtime2) { return (1, "File $file is newer than the compiled version."); } } else { return (1, "Missing file mtime for file $file"); } } else { return (1, "Referenced file $file not found."); } } return 0; } else { return (1, "Could not find file list."); } } sub expandString { # Expand any variables in the datasource definitions for a target. my($str, $wrt, $w) = @_; # Replace all %variables% my($name, $repl); while ( $str =~ /%([^\s%]*)%/ ) { $name = $1; $repl = $wrt->{lc($name)}; if ( defined $repl ) { $str =~ s/%$name%/$repl/; } else { my($sstr) = $str; if (length($sstr) > 20) { $sstr = substr($sstr, 0, 17) . "..."; }; &{$w}("Found unknown tag '$name' during expansion of '$sstr'."); # mark it as not found $str =~ s/%$name%/!$name!/; } } return $str; } sub evalString { # handle any {}'s in the string, which get eval'd my($str, $w) = @_; # Replace all {expr}'s while ( $str =~ /^(.*)\{([^%]*)\}(.*)$/ ) { my($before, $expr, $after) = ($1, $2, $3); my($repl); my(@res) = eval("package Runtime; $expr"); if ($@) { &{$w}("Problem during eval of $expr: $@"); $repl = "##error##"; } else { $repl = join(", ", @res); } $str = $before . $repl . $after; } return $str; } sub expandHash { my($hash, $wrt, $w) = @_; my($k); foreach $k (keys(%{$hash})) { my $hp = \$hash->{$k}; if (index($$hp, "%") >= 0) { $$hp = expandString($$hp, $wrt, $w); } if (index($$hp, "{") >= 0) { $$hp = evalString($$hp, $w); } } } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/lib/ConfigTree/Node.pm0100644000114300000000000003632007511161170016130 0ustar bertdwheel# Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. package ConfigTree::Node; use strict; use Text::ParseWords; use Date::Parse; use FileHandle; use DB_File; use POSIX; my($gDebug) = 0; # tokens which need no name my(%gSkipName) = ( 'oid' => 1, 'rra' => 1, 'html' => 1, 'color' => 1); my(%gTextTags) = ( 'html' => 1 ); sub Name { shift->_getAndSet('Name', @_) }; sub Base { shift->_getAndSet('Base', @_) }; sub Next { shift->_getAndSet('Next', @_) }; sub Prev { shift->_getAndSet('Prev', @_) }; sub File { shift->_getAndSet('File', @_) }; sub NodeCfg { shift->_getAndSet('NodeCfg', @_) }; sub Parent { shift->_getAndSet('Parent', @_) }; sub Files { shift->_getAndSet('Files', @_) }; sub Preload { shift->_getAndSet('Preload', @_) }; sub Done { shift->_getAndSet('Done', @_) }; sub Dir { shift->_getAndSet('Dir', @_) }; # logging callbacks sub info { shift->_getAndSet('info', @_) }; sub warn { shift->_getAndSet('warn', @_) }; sub debug { shift->_getAndSet('debug', @_) }; sub _getAndSet { my($self, $field, $value) = @_; my($retval) = $self->{$field}; $self->{$field} = $value if ($#_ >= 2); return $retval; } sub new { my($class, $tmpl) = @_; my($self) = {}; bless($self, $class); # init the local config to an empty hash, so it's ready to fill # later (in parseLines()). $self->NodeCfg({}); # if we have a template object, copy some interesting things # from it if (defined($tmpl)) { $self->info($tmpl->info()); $self->warn($tmpl->warn()); $self->debug($tmpl->debug()); $self->Base($tmpl->Base()); $self->Files($tmpl->Files()); # copy the preload stuff in, if necessary if ($tmpl->Preload()) { $self->Debug("Got preload..."); my($fm) = $tmpl->Preload()->NodeCfg(); my($to) = $self->NodeCfg(); my($dict, $name, $tag, $v1, $v2, $v3); while (($dict, $v1) = each(%{$fm})) { while (($name, $v2) = each(%{$v1})) { while (($tag, $v3) = each(%{$v2})) { $to->{$dict}->{$name}->{$tag} = $v3; $self->Debug("$dict:$name:$tag = $v3"); } } } } } return $self; } sub init { my($self, $name) = @_; if (! defined($name)) { # this is the first call to init(). $name = '/'; $self->Files({}); $self->Info("Config directory is " . $self->Base()); } $self->Debug("Setting name to $name"); $self->Name($name); my($dir) = $self->Base() . $self->Name(); $dir =~ s/\/$//; my($item, @files, @dirs, $def); foreach $item (<$dir/*>) { if ($item =~ /\/Defaults$/ && -f $item) { $def = $item; } elsif ($item =~ /\~$/) { $self->Warn("Skipping probable backup file: $item"); } elsif (-f $item) { push @files, $item unless $self->skipFile($item); } elsif (-d $item) { push @dirs, $item unless $self->skipFile($item); } else { $self->Warn("Unknown object type for $item."); } } if ($def) { $self->_readFile($def); } foreach $item (@files) { $self->_readFile($item, 1); } foreach $item (@dirs) { my($path, $dirName) = ($item =~ /^(.*)\/(.*)$/); my($new) = new ConfigTree::Node $self; $new->Dir(1); my($newName) = $self->Name() . "/" . $dirName; $newName =~ s#^\/\/#\/#; $new->init($newName); $self->addChild($new); } } sub dump { my($self) = @_; $self->doTree( sub { $self->Info((" " x $_[2]) . "Name: " . $_[0]->Name()) } ); return; } sub compile { my($self, $base) = @_; # default to the config tree's base $base = $self->Base() unless ($base); my($file) = "$base/config.db.new"; my($finalFile) = "$base/config.db"; my($errorLevel); # we are being asked to do a complete rebuild, so start # from scratch unlink($file); my(%db); my($dbh) = tie %db, 'DB_File', $file, O_CREAT|O_RDWR, 0644, $DB_BTREE; my($ct) = $self->compileTree(\%db); # put the entire set of files into the compiled form, so that # we can compare the mtimes later and recompile if necessary my($f); my($filesRef) = $self->Files(); my(@f) = keys(%{$filesRef}); foreach $f (@f) { $db{"f:$f"} = $filesRef->{$f}; } $db{"F:"} = join(',', @f); undef $dbh; untie %db; rename($file, $finalFile) or $errorLevel = $!; if ($errorLevel) { Common::Log::Error("config.db.new could not be renamed to config.db!"); Common::Log::Error("Reason is: $errorLevel"); Common::Log::Error("This is usually due to collector locking config.db."); Common::Log::Error("Your changes won't take effect until this is resolved!"); } return ($ct, $#f+1); } sub compileTree { my($self, $dbref) = @_; my($ct) = 0; $self->compileNode($dbref); $ct++; my($child); foreach $child ($self->getChildren()) { $ct += $child->compileTree($dbref); } return $ct; } sub compileNode { my($self, $dbRef) = @_; # put the data from the config hash into the db, along with # enough data to let us avoid seq-ing over it. We don't want to # use seq, since it's Btree-specific, and we don't want to # stick people with that. (They should be able to use (ugh) dbm if # they need to.) my($node) = $self->Name(); my($cfg) = $self->NodeCfg(); my($dict, $name, $tag, $v, @dicts, @names, @tags); @dicts = (); foreach $dict (keys(%{$cfg})) { @names = (); foreach $name (keys(%{$cfg->{$dict}})) { @tags = (); foreach $tag (keys(%{$cfg->{$dict}->{$name}})) { $dbRef->{"d:$node:$dict:$name:$tag"} = $cfg->{$dict}->{$name}->{$tag}; push @tags, $tag; } $dbRef->{"t:$node:$dict:$name"} = join(',', @tags); push @names, $name; } #$dbRef->{"n:$node:$dict"} = join(',', @names); push @dicts, $dict; } #$dbRef->{"D:$node"} = join(',', @dicts); # put a comma-separated list of the relative names of the children # into a "c:" key in the db. my($child, @children); foreach $child ($self->getChildren()) { push @children, $child->Name(); } $dbRef->{'c:' . $node } = join(',', @children); # tuck the parent into a "p:" key. if ($self->Parent()) { $dbRef->{'p:' . $node } = $self->Parent()->Name(); } else { $dbRef->{'p:' . $node } = ''; } if ($self->Dir()) { $dbRef->{'r:' . $node } = 1; } # Just to recap: # d is for data # t is for tags # n is for names # D is for dicts # c if for a list of children # p is for the name of the parent # r:$name is 1 when this node is a directory (lets us ignore empty # directories later) # f:file => mtime # F: => comma separated list of files } sub processTree { my($self) = @_; $self->processNode(); my($child); foreach $child ($self->getChildren()) { if ( $child ne 'CVS' ) { $child->processTree(); } } } # Here we do any post-processing of the config that we desire. # Right now, we just parse the event dates into times, # so that the grapher does not have to. sub processNode { my($self) = @_; my($name) = $self->Name(); my($cfg) = $self->NodeCfg(); my($evRef) = $cfg->{'event'}; if ($evRef) { my($evName); foreach $evName (keys(%{$evRef})) { my($evDate) = $evRef->{$evName}->{'date'}; if ($evDate && !defined($evRef->{$evName}->{'time'})) { my($t) = str2time($evDate); if (! defined($t)) { $self->Warn("Could not parse date $evDate ". "for event $evName"); } else { $evRef->{$evName}->{'time'} = $t; $self->Debug("date string $evDate for $evName becomes time $t"); } } } } } sub addChild { my($self, @children) = @_; my($child); foreach $child (@children) { $child->Parent($self); } push @{$self->{'Children'}}, @children; return; } sub getChildren { my($self) = @_; if ($self->{'Children'}) { return @{$self->{'Children'}}; } else { return (); } } sub _readFile { my($self, $file, $leaf) = @_; my($buffer); # $self->Debug("Processing file: $file"); $self->File($file); my($fh) = new FileHandle; if (! $fh->open("<$file")) { $self->Warn("Cannot parse $file: $!"); } else { my($line); while (defined($line = <$fh>)) { chomp($line); # handle comments and blank lines $line =~ s/^\s*#.*$//; next if ($line =~ /^\s*$/); if ($line !~ /^\s/) { # this is an initial line $self->parseLines($buffer, $leaf) if $buffer; $buffer = $line; } else { # this is a continuation line $buffer .= "\n"; $buffer .= $line; } } } $self->parseLines($buffer, $leaf) if $buffer; my($mtime) = (stat($fh))[9]; if (! defined($mtime)) { $self->Warn("Could not get mtime for file $file."); } else { ($self->Files())->{$file} = $mtime; } $fh->close(); } sub parseLines { my($self, $lines, $leaf) = @_; my(@words); my($at) = "at (or before) " . $self->File() . " line ${.}."; $lines =~ s/\s*$//; eval { local $SIG{'__DIE__'}; @words = quotewords('[\s=]+', 0, $lines); }; # make unmatched quote errors that quotewords throws # easier to find if ($@ =~ /Unmatched/) { $@ =~ s/ at .*$//; $@ =~ s/\n//; $self->Warn("$@ $at"); return; } my($token) = lc(shift @words); if (! defined($token)) { $self->Warn("Missing token $at"); return; } my($isText) = $gTextTags{$token}; my($name); if ($isText || $gSkipName{$token}) { # it was the CD I was listening to at the time... sue me. $name = '--merril--'; } else { $name = lc(shift @words); if (! defined($name)) { $self->Warn("Missing $token name $at"); return; } } # forge a dictionary if this is a text tag, so that the # coming code can handle it without changes. if ($isText) { my($junk, $key, $text) = split(/\s+/, $lines, 3); @words = ($key, $text); } # make certain there's a valid dict left to parse. if (!$isText && ($#words+1) % 2) { $self->Warn("Missing equals sign $at"); return; } my($node); if ($token eq 'target') { if ($name eq '--default--') { if ($self->Done()) { if (! $self->Preload()) { $self->Debug("Making a preload node."); $self->Preload(new ConfigTree::Node); } $node = $self->Preload(); $self->Debug("Using a preload node."); } else { $node = $self; } } else { $node = new ConfigTree::Node $self; $node->Name($self->Name() . "/$name"); $self->addChild($node); } } else { $node = $self; } # all this mess is to get a reference to a hash where # the parser will be allowed to scribble. my($cfgRef) = $node->NodeCfg(); if (! defined($cfgRef->{$token})) { $cfgRef = ($cfgRef->{$token} = {}); } else { $cfgRef = $cfgRef->{$token}; } # if the key does not exist already... create an empty # key for it. This is so that in the unlikely case there # are no defaults and no attributes, a hash will still # get created as a placeholder, to be correct. if (! defined($cfgRef->{$name})) { $cfgRef->{$name} = {}; } my($k, $v); # now, take the stuff from the @words array and add to the # hash under construction. Unless the value is precisely # "undef", then we delete that key. while ($#words != -1) { $k = lc(shift @words); $v = shift @words; if ($v eq 'undef') { delete($cfgRef->{$name}->{$k}); next; } $cfgRef->{$name}->{$k} = $v; } $self->Done(1); return 1; } sub getNode { my($self, $nodeName) = @_; if ($nodeName eq $self->Name()) { return $self; } else { my($child); foreach $child ($self->getChildren()) { my($res) = $child->getNode($nodeName); return $res if (defined($res)); } return; } } sub Debug { my($self, $msg) = @_; $msg = "[" . ($self->Name() ? $self->Name() : "?") . "] $msg"; if (defined($self->{'debug'})) { &{$self->{'debug'}}($msg); } else { CORE::warn("DEBUG: " . $msg . "\n") if ($gDebug); } } sub Info { my($self, $msg) = @_; if (defined($self->{'info'})) { &{$self->{'info'}}($msg); } else { CORE::warn($msg . "\n"); } } sub Warn { my($self, $msg) = @_; if (defined($self->{'warn'})) { &{$self->{'warn'}}($msg); } else { CORE::warn("Warning: " . $msg . "\n"); } } sub doTree { my($self, $cb, $state, $level) = @_; $level = 0 unless(defined($level)); &{$cb}($self, $state, $level); my($child); foreach $child ($self->getChildren()) { $child->doTree($cb, $state, $level+1); } } sub isLeaf { my($self) = @_; return (!defined($self->{'Children'})); } sub break { print "Splero!\n" }; sub skipFile { my($self, $file) = @_; my($res) = 0; $res = 1 if ($file =~ /\/#.*#$/); $res = 1 if ($file =~ /\/README$/); $res = 1 if ($file =~ /\.bak$/); $res = 1 if ($file =~ /\.lock$/); $res = 1 if ($file =~ /\.new$/); $res = 1 if ($file =~ /\/RCS$/); $res = 1 if ($file =~ /,v$/); $res = 1 if ($file =~ /~$/); $res = 1 if ($file =~ /\/config.db$/); $res = 1 if ($file =~ /\/config.db.new$/); $res = 1 if ($file =~ /\.dpkg-/); $res = 1 if ($file =~ /^\./); $res = 1 if ($file =~ /\/CVS$/); return $res; } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/lib/RRD/0040755000114300000000000000000010031605166013305 5ustar bertdwheelcricket-1.0.5/lib/RRD/File.pm0100644000114300000000000003130307771714042014532 0ustar bertdwheel# -*- perl -*- # RRD::File: a package for digging around in RRD files from Perl # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. package RRD::File; use strict; use Carp; use Config; use FileHandle; use RRD::Format; $RRD::File::gErr = ""; $RRD::File::gArch = $Config{'archname'}; # field setters/getters (gets value if no args, sets value and # returns old value if it does have an arg) sub file { shift->_getAndSet('file', @_) }; sub fh { shift->_getAndSet('fh', @_) }; sub fmt { shift->_getAndSet('format', @_) }; sub ds_cnt { shift->_getAndSet('ds_cnt', @_) }; sub rra_cnt { shift->_getAndSet('rra_cnt', @_) }; sub pdp_step { shift->_getAndSet('pdp_step', @_) }; sub cdp_xff { shift->_getAndSet('cdp_xff', @_) }; sub last_up { shift->_getAndSet('last_up', @_) }; # this is file property rather than an architecture format property sub version { shift->_getAndSet('version', @_) }; sub _getAndSet { my($self, $field, $value) = @_; my($retval) = $self->{$field}; $self->{$field} = $value if ($#_ >= 2); return $retval; } sub new { my($type) = shift; my(%p) = @_; my($self) = {}; $self->{'file'} = $p{'-file'}; $self->{'fh'} = undef; bless $self, $type; return $self; } sub open { my($self, $mode) = @_; $mode = "<" if (! defined($mode)); my($file) = $self->file(); return unless defined($file); my($fh) = new FileHandle; if (! $fh->open($mode . $file)) { $fh = undef; } else { binmode($fh); } $self->fh($fh); return $fh; } sub close { my($self) = @_; return unless $self->fh(); $self->fh()->close(); $self->fh(undef); return; } # Reading the RRD headers is quite efficient, since it's using stdio. # we typically read a disk block starting at offset # 0, then fetch all of the header stuff from there. On my Solaris # machine truss shows that we make one 8k read for the header, # then one 8k read per DS we want to dig into. sub _readBlock { my($self, $off, $size) = @_; my($fh) = $self->fh(); return undef unless $fh; $fh->seek($off, 0) unless (! defined($off)); my($buf); if ($fh->read($buf, $size)) { return $buf; } else { return undef; } } sub _readNextBlock { my($self, $size) = @_; return $self->_readBlock(undef, $size); } sub loadHeader { my($self) = @_; my($fh) = $self->fh(); my($fmt) = new RRD::Format; if (! $fmt->setArch($RRD::File::gArch)) { $RRD::File::gErr = "Architecture $RRD::File::gArch not supported yet."; return; } # save it for later use (esp by getDataOffset) $self->fmt($fmt); my($header) = $self->_readBlock(0, sizeof($fmt->format('statHead'))); return undef unless (defined($header)); # cdp_xff is only available with old format, but it's only # accessed byu callers when they know it's going to be set, # so it's OK if we set it tp undef here for new format RRD's. my($c, $v, $fc, $ds_cnt, $rra_cnt, $pdp_step, $cdp_xff) = unpack($fmt->format('statHead'), $header); $c = fixName($c); $v = fixName($v); if ($c ne $fmt->format('cookie') || ("$fc" != $fmt->format('float_cookie'))) { $RRD::File::gErr = "Something is wrong with the header of this file."; return; } $self->version($v); # and save all the good stuff for later use $self->ds_cnt($ds_cnt); $self->rra_cnt($rra_cnt); $self->pdp_step($pdp_step); $self->cdp_xff($cdp_xff); my($i); foreach $i (0 .. $ds_cnt-1) { my(%def) = (); my($block) = $self->_readNextBlock(sizeof($fmt->format('dsDef'))); croak("Could not read DS def $i") unless (defined($block)); my($dsName, $dst, $ds_mrhb, $min_val, $max_val) = unpack($fmt->format('dsDef'), $block); $dsName = fixName($dsName); $dst = fixName($dst); %def = ( 'dsName' => $dsName, 'dst' => $dst, 'ds_mrhb' => $ds_mrhb, 'min_val' => $min_val, 'max_val' => $max_val ); push @{$self->{'ds_def'}}, \%def; } foreach $i (0 .. $rra_cnt-1) { my(%def) = (); my($block) = $self->_readNextBlock(sizeof($fmt->format('rraDef'))); croak("Could not read RRA def $i") unless (defined($block)); my($rraName, $row_cnt, $pdp_cnt, $cf) = unpack($fmt->format('rraDef'), $block); $rraName = fixName($rraName); $cf = fixName($cf); %def = ( 'rraName' => $rraName, 'cf' => $cf, 'row_cnt' => $row_cnt, 'pdp_cnt' => $pdp_cnt ); push @{$self->{'rra_def'}}, \%def; } { my ($block); my ($last_up, $last_up_usec); if ($v eq "0001" || $v eq "0002") { $block = $self->_readNextBlock(sizeof($fmt->format('liveHead'))); croak("Could not read live header") unless (defined($block)); $last_up = unpack($fmt->format('liveHead'), $block); } else { if (!defined($fmt->format('liveHead3'))) { $RRD::File::gErr = "RRD file version " . $v . " not supported on this arch."; return; } $block = $self->_readNextBlock(sizeof($fmt->format('liveHead3'))); croak("Could not read live header") unless (defined($block)); ($last_up, $last_up_usec) = unpack($fmt->format('liveHead3'), $block); } $self->last_up($last_up); } foreach $i (0 .. $ds_cnt-1) { my(%def) = (); my($block) = $self->_readNextBlock(sizeof($fmt->format('pdpDef'))); croak("Could not read PDP $i") unless (defined($block)); my($last_ds, $unkn_sec, $value) = unpack($fmt->format('pdpDef'), $block); $last_ds = fixName($last_ds); %def = ('last_ds' => $last_ds, 'value' => $value, 'unkn_sec' => $unkn_sec ); push @{$self->{'pdps'}}, \%def; } foreach $i (0 .. (($ds_cnt * $rra_cnt)-1)) { my(%def) = (); my($block) = $self->_readNextBlock(sizeof($fmt->format('cdpDef'))); croak("Could not read CDP $i") unless (defined($block)); my($value, $unkn_pdp) = unpack($fmt->format('cdpDef'), $block); %def = ( 'value' => $value, 'unkn_pdp' => $unkn_pdp ); push @{$self->{'cdps'}}, \%def; } foreach $i (0 .. ($rra_cnt-1)) { my($block) = $self->_readNextBlock(sizeof($fmt->format('rraPtr'))); croak("Could not read RRD cur $i") unless (defined($block)); my($ptr) = unpack($fmt->format('rraPtr'), $block); push @{$self->{'rra_ptr'}}, $ptr; } return 1; } sub ds_def { my($self, $i) = @_; return $self->{'ds_def'}->[$i]; } sub rra_def { my($self, $i) = @_; return $self->{'rra_def'}->[$i]; } sub pdp { my($self, $i) = @_; return $self->{'pdps'}->[$i]; } sub cdp { my($self, $i) = @_; return $self->{'cdps'}->[$i]; } sub rra_ptr { my($self, $i) = @_; return $self->{'rra_ptr'}->[$i]; } sub getDSRowValue { my($self, $rra, $row, $ds) = @_; my($ds_cnt) = $self->ds_cnt(); my($rra_cnt) = $self->rra_cnt(); if (!defined($ds) || $ds >= $self->ds_cnt() || !defined($rra) || $rra >= $self->rra_cnt()) { return; } my($fmt) = new RRD::Format; if (! $fmt->setArch($RRD::File::gArch)) { $RRD::File::gErr = "Architecture $RRD::File::gArch not supported yet."; return; } $self->fmt($fmt); # this gets us to the place in the file where the data starts. my($headerOffset) = $self->getDataOffset(); # was there an error? return unless($headerOffset); # determine which row we want from the RRA my($numRows) = $self->rra_def($rra)->{row_cnt}; my($wantedRow) = ($self->rra_ptr($rra) - $row); while($wantedRow < 0) { $wantedRow += $numRows; } my($elmSize) = sizeof($fmt->format('element')); my $rraOffset = 0; my $i; # jump over intervening rras for ($i = 0; $i < $rra; $i++) { $numRows = $self->rra_def($i)->{row_cnt}; $rraOffset += $ds_cnt * $numRows * $elmSize; } my($dataOffset) = $rraOffset + $wantedRow * ($ds_cnt * $elmSize) + $ds * $elmSize; my($offset) = $headerOffset + $dataOffset; my($value) = $self->_readBlock($offset, $elmSize); if (defined($value)) { ($value) = unpack($fmt->format('element'), $value); return $value; } else { return; } } sub getDSCurrentValue { my($self, $ds) = @_; my($ds_cnt) = $self->ds_cnt(); my($rra_cnt) = $self->rra_cnt(); # check param, now that we have ds_cnt. if (!defined($ds) || $ds >= $self->ds_cnt()) { return; } my($fmt) = new RRD::Format; if (! $fmt->setArch($RRD::File::gArch)) { $RRD::File::gErr = "Architecture $RRD::File::gArch not supported yet."; return; } $self->fmt($fmt); # this gets us to the place in the file where the data starts. now # all we need to do is find our bit of data within the data area... my($headerOffset) = $self->getDataOffset(); # was there an error? return unless ($headerOffset); # now, calculate the offset into the data area # we want the current row in the first RRA. # we want the column corresponding to the given ds. # (see last bit of rrd_format.h # if you want to try to understand this...) my($wantedRow) = $self->rra_ptr(0); my($elmSize) = sizeof($fmt->format('element')); my($rraOffset) = 0; # this is because we only want the first RRA. # eventually, we could compute this for an # arbitrarty RRA. my($dataOffset) = $rraOffset + $wantedRow * ($ds_cnt * $elmSize) + $ds * $elmSize; my($offset) = $headerOffset + $dataOffset; my($value) = $self->_readBlock($offset, $elmSize); if (defined($value)) { ($value) = unpack($fmt->format('element'), $value); return $value; } else { return; } } sub getDataOffset { my($self) = @_; my($fmt) = $self->fmt(); return unless (defined($fmt)); my($ds_cnt) = $self->ds_cnt(); my($rra_cnt) = $self->rra_cnt(); my($lhSz) = ($self->version() ne "0003") ? sizeof($fmt->format('liveHead')) : sizeof($fmt->format('liveHead3')); return (sizeof($fmt->format('statHead')) + $ds_cnt * sizeof($fmt->format('dsDef')) + $rra_cnt * sizeof($fmt->format('rraDef')) + $lhSz + $ds_cnt * sizeof($fmt->format('pdpDef')) + ($ds_cnt * $rra_cnt) * sizeof($fmt->format('cdpDef')) + $rra_cnt * sizeof($fmt->format('rraPtr'))); } sub getMeta { my($self) = @_; my($metaRef) = {}; my($file) = $self->file(); return $metaRef unless defined($file); # make the metafile name. Remove the .rrd (if there is one) # and append .meta $file =~ s/\.rrd$//; my($metaFile) = "$file.meta"; if (CORE::open(META, "<$metaFile")) { while () { chomp; my($delim) = "\0"; $delim = ":" if (! /\0/); # backwards compatiblity my($k, $v) = split(/$delim/, $_, 2); $metaRef->{$k} = $v; } CORE::close(META); } return $metaRef; } sub setMeta { my($self, $metaRef) = @_; my($file) = $self->file(); return unless defined($file); # make the metafile name. Remove the .rrd (if there is one) # and append .meta $file =~ s/\.rrd$//; my($metaFile) = "$file.meta"; if (CORE::open(META, ">$metaFile")) { my($k); foreach $k (keys(%{$metaRef})) { print META join("\0", $k, $metaRef->{$k}), "\n"; } CORE::close(META); return 1; } else { return; } } sub fixName { my($str) = @_; # fix undefs (generated by old files) to be emtpy, so that # we don't get warnings in rrd-dump return "" unless defined ($str); # even though unpack gives us all the bytes, we only want the C # string. my($nul) = index($str, "\0"); if ($nul != -1) { $str = substr($str, 0, $nul); } return $str; } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/lib/RRD/Format.pm0100644000114300000000000001456210003357675015110 0ustar bertdwheel# -- perl -- # RRD::Format: constants useful when digging around in an RRD file # from Perl # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # PORTING: # # Because RRD datafiles are defined in terms of C structures, # different C compilers will pad them differently. Thus, this # file needs to be updated to understand the various file # layouts. To add support for an unsupported architecture, # add a section to setArch that matches your architecture. # The simple C program getFormat.c (which is in the util directory) # can probably be trusted to find the correct format strings # for your architecture. Instructions on how to use it are in # the comments at the beginning of the program. # Please send patches in, as you find # formats for your specific architecture. package RRD::Format; require Exporter; @ISA = qw(Exporter); @EXPORT = qw(sizeof); sub new { my($class) = @_; my($self) = {}; bless($self, $class); return $self; } sub format { my($self, $fmt) = @_; return $self->{$fmt}; } sub isOld { my($str) = @_; return ($str eq "mrtg"); } sub setArch { my($self, $archname) = @_; $self->{'cookie'} = "RRD"; $self->{'float_cookie'} = 8.642135e130; $self->{'DST_COUNTER'} = 0; $self->{'DST_ABSOLUTE'} = 1; $self->{'DST_GAUGE'} = 2; $self->{'DST_DERIVE'} = 3; $self->{'CF_AVERAGE'} = 0; $self->{'CF_MINIMUM'} = 1; $self->{'CF_MAXIMUM'} = 2; # new cf functions $self->{'CF_LAST'} = 3; $self->{'CF_HWPREDICT'} = 4; $self->{'CF_SEASONAL'} = 5; $self->{'CF_DEVPREDICT'} = 6; $self->{'CF_DEVSEASONAL'} = 7; $self->{'CF_FAILURES'} = 8; $self->{'LAST_DS_LEN'} = 30; # Tobias says, "DO NOT CHANGE THIS" :) $archname =~ s/-multi-thread//g; $archname =~ s/-thread-multi//g; $archname =~ s/-thread//g; if ($archname eq "sun4-solaris" || $archname eq "MSWin32-x86" || $archname eq "MSWin32-x86-object" || $archname eq "irix-o32" || $archname eq "sparc-linux") { $self->{'newOld'} = "A4"; $self->{'statHead'} = "a4 a5 x7 d L L L x4 x80"; $self->{'dsDef'} = "a20 a20 L x4 d d x56"; $self->{'rraDef'} = "a20 L L x4 d x72"; $self->{'pdpDef'} = "a30 x2 L x4 d x64"; $self->{'cdpDef'} = "d L x4 x64"; $self->{'liveHead'} = "L"; $self->{'liveHead3'} = "L L"; $self->{'rraPtr'} = "L"; $self->{'element'} = "d"; return 1; } elsif ($archname =~ /^aix|i.?86/i) { # Matija.Grabnar@arnes.si says that these formats # work for Solaris x86 too. And Patrick Myers # says it works for i386-freebsd. Cool. And John Banner # says it works on AIX. And Dave Mangot # says it works on i386-openbsd. Peter Evans # found a bug with Solaris 8/x86 which has been # fixed. # So, let's simplify and just say, 'if it has i.86 in the name, # use this format. $self->{'newOld'} = "A4"; $self->{'statHead'} = "a4 a5 x3 d L L L x80"; $self->{'dsDef'} = "a20 a20 L x4 d d x56"; $self->{'rraDef'} = "a20 L L d x72"; $self->{'pdpDef'} = "a30 x2 L x4 d x64"; $self->{'cdpDef'} = "d L x4 x64"; $self->{'liveHead'} = "L"; $self->{'rraPtr'} = "L"; $self->{'element'} = "d"; return 1; } elsif ( $archname eq 'alpha-dec_osf') { # Thanks to Melissa D. Binde for # finding this (and a major foobar in getFormat.c) $self->{'statHead'} = "a4 a5 x7 d L L L x80"; $self->{'dsDef'} = "a20 a20 L d d x56"; $self->{'rraDef'} = "a20 L L d x72"; $self->{'pdpDef'} = "a30 x2 L d x64"; $self->{'cdpDef'} = "d L x64"; $self->{'liveHead'} = "L"; $self->{'rraPtr'} = "L"; $self->{'element'} = "d"; } elsif ( $archname =~ 'PA-RISC' or $archname eq 'powerpc-linux') { $self->{'statHead'} = "a4 a5 x7 d L L L x4 x80"; $self->{'dsDef'} = "a20 a20 L x4 d d x56"; $self->{'rraDef'} = "a20 L L x4 d x72"; $self->{'pdpDef'} = "a30 x2 L x4 d x64"; $self->{'cdpDef'} = "d L x4 x64"; $self->{'liveHead'} = "L"; $self->{'rraPtr'} = "L"; $self->{'element'} = "d"; } elsif ( $archname eq 'alpha-linux' ) { $self->{'statHead'} = "a4 a5 x7 d Q Q Q x80"; $self->{'dsDef'} = "a20 a20 Q d d x56"; $self->{'rraDef'} = "a20 x4 Q Q d x72"; $self->{'pdpDef'} = "a30 x2 Q d x64"; $self->{'cdpDef'} = "d Q x64"; $self->{'liveHead'} = "Q"; $self->{'rraPtr'} = "Q"; $self->{'element'} = "d"; } elsif ( $archname eq 'sparc64-netbsd' ) { $self->{'statHead'} = "a4 a5 x7 d Q Q Q x80"; $self->{'dsDef'} = "a20 a20 Q d d x56"; $self->{'rraDef'} = "a20 x4 Q Q d x72"; $self->{'pdpDef'} = "a30 x2 Q d x64"; $self->{'cdpDef'} = "d Q x64"; $self->{'liveHead'} = "L"; $self->{'rraPtr'} = "Q"; $self->{'element'} = "d"; } elsif ( $archname eq 's390x-linux' ) { $self->{'statHead'} = "a4 a5 x7 d Q Q Q x80"; $self->{'dsDef'} = "a20 a20 Q d d x56"; $self->{'rraDef'} = "a20 x4 Q Q d x72"; $self->{'pdpDef'} = "a30 x2 Q d x64"; $self->{'cdpDef'} = "d Q x64"; $self->{'liveHead'} = "Q"; $self->{'rraPtr'} = "Q"; $self->{'element'} = "d"; } else { return; } } # a utility subroutine to imitate C's sizeof() built-in sub sizeof { my($template) = @_; return (length(pack($template))); } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/lib/alternate/0040755000114300000000000000000010031605166014635 5ustar bertdwheelcricket-1.0.5/lib/alternate/RRD/0040755000114300000000000000000010031605166015264 5ustar bertdwheelcricket-1.0.5/lib/alternate/RRD/File.pm0100644000114300000000000001650107610356513016511 0ustar bertdwheel# -*- perl -*- # RRD::File: a package for digging around in RRD files from Perl # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. package RRD::File; use strict; use Carp; use Config; use FileHandle; use RRDs; $RRD::File::gErr = ""; $RRD::File::gArch = $Config{'archname'}; # field setters/getters (gets value if no args, sets value and # returns old value if it does have an arg) sub file { shift->_getAndSet('file', @_) }; sub fh { shift->_getAndSet('fh', @_) }; sub fmt { shift->_getAndSet('format', @_) }; sub ds_cnt { shift->_getAndSet('ds_cnt', @_) }; sub rra_cnt { shift->_getAndSet('rra_cnt', @_) }; sub pdp_step { shift->_getAndSet('pdp_step', @_) }; sub cdp_xff { shift->_getAndSet('cdp_xff', @_) }; sub last_up { shift->_getAndSet('last_up', @_) }; sub info { shift->_getAndSet('info', @_) }; sub _getAndSet { my($self, $field, $value) = @_; my($retval) = $self->{$field}; $self->{$field} = $value if ($#_ >= 2); return $retval; } sub new { my($type) = shift; my(%p) = @_; my($self) = {}; $self->{'file'} = $p{'-file'}; $self->{'fh'} = undef; bless $self, $type; return $self; } sub open { my($self, $mode) = @_; $mode = "<" if (! defined($mode)); my($file) = $self->file(); return unless defined($file); my($fh) = new FileHandle; if (! $fh->open($mode . $file)) { $fh = undef; } else { binmode($fh); } close($fh) if $fh; return 1; } sub close { my($self) = @_; 1; } sub loadHeader { my($self) = @_; my($file) = $self->file(); my($info) = RRDs::info($file); if (!$info) { $RRD::File::gErr = "Error reading RRD header: " . RRDs::error; return; } $self->info($info); # cdp_xff is only available with old format, but it's only # accessed byu callers when they know it's going to be set, # so it's OK if we set it tp undef here for new format RRD's. my %DSdefs; my %RRAdefs; while (my ($key, $value) = each(%$info)) { if ($key =~ /^ds\[(.*)\]\.(.*)$/) { $value = fixName($value) if ($2 eq 'type'); $DSdefs{fixName($1)}{$2} = $value; # warn "dsdefs{$1}{$2} = $value\n"; } elsif ($key =~ /^rra\[(.*)\]\.(.*)$/) { $RRAdefs{fixName($1)}{$2} = $value; # warn "rradefs{$1}{$2} = $value\n"; } } $self->ds_cnt( scalar(keys %DSdefs) ); $self->rra_cnt( scalar(keys %RRAdefs) ); $self->pdp_step( $$info{'step'} ); foreach my $dsName (keys %DSdefs) { my $ds = $DSdefs{$dsName}; my(%def) = (); my $dsNum = ($dsName =~ /^ds(\d+)$/)[0]; my($dst, $ds_mrhb, $min_val, $max_val, $value) = ($$ds{type}, $$ds{minimal_heartbeat}, $$ds{min}, $$ds{max}, $$ds{value}); %def = ( 'dsName' => $dsName, 'dst' => $dst, 'ds_mrhb' => $ds_mrhb, 'min_val' => $min_val, 'max_val' => $max_val, 'value' => $value ); $self->{'ds_def'}->[$dsNum] = \%def; } foreach my $rraName (keys %RRAdefs) { my $rra = $RRAdefs{$rraName}; my(%def) = (); my($row_cnt, $pdp_cnt, $cf) = ($$rra{rows}, $$rra{pdp_per_row}, $$rra{cf}); $rraName = fixName($rraName); $cf = fixName($cf); %def = ( 'rraName' => $rraName, 'cf' => $cf, 'row_cnt' => $row_cnt, 'pdp_cnt' => $pdp_cnt ); push @{$self->{'rra_def'}}, \%def; } $self->last_up($$info{last_update}); foreach my $dsName (keys %DSdefs) { my(%def) = (); my($last_ds, $unkn_sec, $value) = ($DSdefs{$dsName}{last_ds}, $DSdefs{$dsName}{value}, $DSdefs{$dsName}{unknown_sec}); $last_ds = fixName($last_ds); %def = ('last_ds' => $last_ds, 'value' => $value, 'unkn_sec' => $unkn_sec ); push @{$self->{'pdps'}}, \%def; } return 1; } sub ds_def { my($self, $i) = @_; return $self->{'ds_def'}->[$i]; } sub rra_def { my($self, $i) = @_; return $self->{'rra_def'}->[$i]; } sub pdp { my($self, $i) = @_; return $self->{'pdps'}->[$i]; } sub cdp { my($self, $i) = @_; return $self->{'cdps'}->[$i]; } sub getDSCurrentValue { my($self, $ds) = @_; my($ds_cnt) = $self->ds_cnt(); # check param, now that we have ds_cnt. if (!defined($ds) || $ds >= $ds_cnt) { return undef; } my ($start, $step, $names, $data) = RRDs::fetch($self->file(), 'AVERAGE', '--start', "now", '--end', "now"); if (my $error = RRDs::error) { Warn("getDSCurrentValue: fetch failed: $error"); return undef; } my $ret = @{$data}[0]->[$ds]; return 'NaN' unless defined($ret); return $ret; } sub getMeta { my($self) = @_; my($metaRef) = {}; my($file) = $self->file(); return $metaRef unless defined($file); # make the metafile name. Remove the .rrd (if there is one) # and append .meta $file =~ s/\.rrd$//; my($metaFile) = "$file.meta"; if (CORE::open(META, "<$metaFile")) { while () { chomp; my($delim) = "\0"; $delim = ":" if (! /\0/); # backwards compatiblity my($k, $v) = split(/$delim/, $_, 2); $metaRef->{$k} = $v; } CORE::close(META); } return $metaRef; } sub setMeta { my($self, $metaRef) = @_; my($file) = $self->file(); return unless defined($file); # make the metafile name. Remove the .rrd (if there is one) # and append .meta $file =~ s/\.rrd$//; my($metaFile) = "$file.meta"; if (CORE::open(META, ">$metaFile")) { my($k); foreach $k (keys(%{$metaRef})) { print META join("\0", $k, $metaRef->{$k}), "\n"; } CORE::close(META); return 1; } else { return; } } sub fixName { my($str) = @_; # fix undefs (generated by old files) to be emtpy, so that # we don't get warnings in rrd-dump return "" unless defined ($str); # even though unpack gives us all the bytes, we only want the C # string. my($nul) = index($str, "\0"); if ($nul != -1) { $str = substr($str, 0, $nul); } return $str; } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # cperl-indent-level: 4 # End: cricket-1.0.5/lib/alternate/net-snmp/0040755000114300000000000000000010031605166016376 5ustar bertdwheelcricket-1.0.5/lib/alternate/net-snmp/snmpUtils.pm0100644000114300000000000001416207503217505020741 0ustar bertdwheel# $Id: snmpUtils.pm,v 1.4 2002/06/16 23:29:09 driehuis Exp $ # $Source: /cvsroot/cricket/cricket/lib/alternate/net-snmp/snmpUtils.pm,v $ # # This is a simple wrapper for Net-SNMP. People who want to # use other SNMP libraries can hook the calls here by replacing this # copy of snmpUtils.pm with their own, which redirects the calls # to their own library. # # To use this one, mv the original snmpUtils.pm out of the way and # symlink this one in its place: # cd ~cricket/lib # mv snmpUtils.pm snmpUtils_SNMP_Session.pm # ln -s alternate/net-snmp/snmpUtils.pm snmpUtils.pm # # TODO: # Verify that traps are generated consistent with the main # implementation. # Provide an upper bound to the number of cached sessions, # by implementing an LRU cache. package snmpUtils; use Common::Log; use SNMP; use Sys::Hostname; # this is the OID for enterprises.webtv.wtvOps.wtvOpsTraps my($trapoid) = ".1.3.6.1.4.1.2595.1.1"; # Max number of times a device can fail to respond before we skip further # requests. Adjust as needed. (This should probably be made a target # attribute in the config tree so it can be set on a per device basis.) my $MAXTRIES = 2; my %skipcnt; my %sessions; my @fifo; my $hostname = undef; sub init { %skipcnt = (); %sessions = (); } # Establish an SNMP session to the given host. sub opensnmp { my($snmp) = @_; if (defined $sessions{$snmp}) { # If we already have a session... if ($sessions{$snmp} != -1) { # If it not blacklisted, return it. return $sessions{$snmp}; } else { # Else blacklisted, return undef. return undef; } } my $snmp_url = $snmp; $snmp =~ s#snmp://##; my $istrap = 0; $istrap = 1 if ($snmp =~ s#trap://##); my ($comm, $rest) = split(/\@/, $snmp, 2); if (!defined($rest)) { $comm = undef; $rest = $snmp; } my ($host, $port, $timeout, $retries, $backoff, $version) = split(/\s*:\s*/, $rest); $comm ||= 'public'; $port ||= 161 if !$istrap; $port ||= 162 if $istrap; $timeout ||= 2; $retries ||= 5; $backoff ||= 1; $version ||= 1; Info("Opening SNMP session to $host:$port/v$version"); my %session_opts = (Community => $comm, DestHost => $host, RemotePort => $port, Timeout => $timeout * 1000000, Retries => $retries, Version => $version, AuthProto => 'MD5', PrivProto => 'DES', AuthPass => '', PrivPass => '', Context => 'default', SecName => 'initial', SecLevel => 'authNoPriv', UseNumeric => 1, UseLongNames => 1); my $session = new SNMP::Session(%session_opts) if !$istrap; $session = new SNMP::TrapSession(%session_opts) if $istrap; if (!defined($session)) { Warn("Can't set up session to $snmp"); $sessions{$snmp} = -1; return undef; } $sessions{$snmp_url} = $session; # Save the session for future reference. $skipcnt{$snmp_url} = $MAXTRIES; # Init the blacklist counter. push @fifo, $snmp_url; if ($#fifo > 20) { my $old_url = shift @fifo; delete $sessions{$old_url}; # We keep the blacklist entry } return $session; } sub count_error { my ($snmp, $session) = @_; # Strip community name from error my($ignore1, $ssnmp) = $snmp =~ /([^@]+@)?(.*)/; my $errstr = $session->{"ErrorStr"}; Warn($errstr); if ($errstr =~ /timeout/i) { $skipcnt{$snmp}--; Warn("Skip count now $skipcnt{$snmp} for $ssnmp"); if ($skipcnt{$snmp} <= 0) { Warn("Blacklisting $ssnmp"); $sessions{$snmp} = -1; } } } sub get { my ($snmp, @oids) = @_; my $session = opensnmp($snmp); return () unless defined($session); my @vars; foreach my $oid (@oids) { my $var = new SNMP::Varbind([$oid]); push @vars, $var; } my $varlist = new SNMP::VarList(@vars); $session->get($varlist); if ($session->{"ErrorNum"}) { &count_error($snmp, $session); return (); } my @return; foreach my $var (@vars) { push @return, $var->val; } return @return; } sub walk { my ($snmp, $oid) = @_; my $session = opensnmp($snmp); return () unless defined($session); my @return = (); $oid = &SNMP::translateObj($oid) if $oid =~ /^[a-zA-Z]/; $oid = ".$oid" unless substr($oid, 0, 1) eq "."; my $var = new SNMP::Varbind([$oid]); while (defined $session->getnext($var)) { last if substr($var->tag, 0, length($oid)) ne $oid; if (length($var->tag) > length($oid)) { push @return, substr($var->tag, length($oid) + 1) . "." . $var->iid . ":" . $var->val; } else { push @return, $var->iid . ":" . $var->val; } } return @return; } sub trap { my($to, $spec, @data) = @_; my @newdata = (); my($ct) = 1; foreach my $item (@data) { push @newdata, ".$ct", "", $item; $ct++; } &trap2($to, $spec, @newdata); } sub trap2 { my($to, $spec, @data) = @_; $to = "trap://$to" unless $to =~ /^trap/; my $session = opensnmp($to); return undef unless defined($session); # this makes a oid->value map for the trap. Note that # we just fake up simple one-level OID's... it suits our needs. my($type, $item, @vars); while (@data) { $oid = shift @data; shift @data; $item = shift @data; $type = OCTETSTR; $type = UINTEGER if ($item =~ /^(\d+)$/); $type = INTEGER if ($item =~ /^-(\d+)$/); my $var = new SNMP::Varbind([$oid, undef, $item, $type]); push @vars, $var; } my $varlist = new SNMP::VarList(@vars); $hostname ||= hostname(); $session->trap(enterprise=>$trapoid, agent=>$hostname, specific=>$spec, $varlist); } 1; # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/CHANGES0100644000114300000000000011351410003360351013076 0ustar bertdwheelNew features, fixes in version 1.0.5 Cricket 1.0.5 is essentially a minor bugfix release. An error with case dependance with new style views was eliminated. A "label" tag was added to the "view" dictionary. This eliminates the need for using spaces in view names. It also allows the Cricket admin to alias different targetTypes to the same name in the HTML menu -- use this feature wisely! systemPerfConf.pl was reverted back to using our own SNMP_Session based SNMP interface, and interfaces detection was added [Mike Han] The distributed sample-config/Defaults has width-hint and height-hint commented out, to cut down on squashed graphics. The handling of NaN was standardized throughout the code. To check for NaN, use isNaN from Common::Util. All text files now have LF line endings. In the 1.0.4 release, a number of files were inadvertantly changed to have CRLF line endings. A small bug in threshold monitoring was identified and fixed by Jase MacLeod. Error reporting by the monitor subsystem has been reviewed and made more extensive, based on a patch by Andrew Clark. If you have scripts that look for the text string "Skipping", you are well advised to look for "Monitor" instead, and review your scripts while you're at it. We now support s390x-linux, thanks to Shane Stixrud. [Patch #877924] New features, fixes in version 1.0.4 Added span option for monitor-thresholds of all types [Patch #838250]. Added new option to log entry format [Patch #565877]. Added new META monitor thresholds action [Patch #821255]. Added support for sparc64-netbsd from Edwin Mons [Patch #803408]. Documented the "use-gprint" target option [Bug #667178]. Documented the "show-path" target option [Bug #667179]. Added support for optionally logging the full path to a datasource in collector output. By setting $gLogFullPath=1 in cricket-sonf.pl you will get e.g. "Switches/Cat4000/core/FastEthernet0_1" instead of just "FastEthernet0_1" in your logs. Added a new "hidden" tag to the "target" dictionary. When this is set to a true value the target will be skipped by the grapher to enable you to display the data combined in an "mtarget" instead. [Patch #697737] Support for package qualification in func data source fetching (patch #772097). Support for RRD version 0003 files on some architectures; the last update microsecond value is not actual available in Cricket (and hence not published in the grapher Summary) but it is accommodated (so that functions like getDSCurrentValue that essentially use pointer arithmetic to jump around the binary RRD files will work). Fix for bug #666675; the "floating point comparison perl 5.8" issue. The most prominent symptom of this issue is the message "Current values not available: Something is wrong with the header of this file" in the grapher summary panel. Documentation for the failures monitor threshold. Permit conditioning a failures monitor threshold on a range. Fix to loading WMI Scripting constants library in wbem.pm. Should allow using WMI on WinXP. Minor fixes to Monitor.pm: detection of NaN/nan, LogToFile duplicate line detection, and new SNMP VarBind (9) to send value in trap. [patch #592165, #592163] An experimental new feature allows you to use the Cricket target name as the rrd datasource name. Some caveats apply: - if you use COMPUTE datasources, you will have to manually edit your config tree, and update all rrd-cdef rules to match the new reality. Do not switch on long ds names unless you're prepared to do this! In particular, users of the NT perfmon tree should not yet enable this. - RRD ds names are limited to 19 characters. Cricket datasource names longer than 15 characters are edited automatically to ensure the RRD ds name remains unique. You can use the Common::Util::mungeDsName routine in your own code to generate compatible munged names (or use target names under 16 characters to avoid the issue altogether). The feature is enabled by putting $gLongDSName=1 in their cricket-conf.pl Introducing the view dictionary! Views are now separate config tree entries. Going forward, the view tag in the targettype dictionary should specify a comma-delimited list of views that are defined in separate view entries. View entries support all non-datasource specific graph tags (aka graph --default-- tags) and view specific tags. The most important view specific tag is 'elements' which specifies a comma-delimited or space-delimited list of data sources. Note that this change should not break existing config trees because the old targettype view tag syntax is still supported. listInterfaces has been updated (users of the contributed web site may recognize listInterfaces+). When upgrading from previous versions of Cricket, it is recommended that you review any scripts that call listInterfaces. The URL style is now selectable from cricket-conf.pl. Options are "classic" (what Cricket 1.0.3 did, default), "relative" (which uses relative URLs, for reverse proxy) and "pathinfo" (passes the target name as part of the URL path, which allows for using Apache access restrictions). It is possible to mark NaN samples in the graph with a colored background. This is enabled via the view tag paint-nan (boolean value). WARNING: do not enable this if you use the STACK draw-as type anywhere in your graphs. Added an alternate SNMP interface, using net-snmp. See lib/alternate/snmpUtils.pm. Not recommended in its current incarnation for users of SNMP traps and threshold monitoring. On some systems, using it may speed up Cricket by 10-15%. Fix mtargets bugs w.r.t. MiXeD CaSe names (hopefully for good) [bug #521367] Fix graph scaling with explicit "bytes=false", which was misinterpreted by the graphing code. Updated getFormat.c from John Woolner [Patch #454472]. New features, fixes in version 1.0.3 The many disk accesses to the config database that collector uses are causing the collector to run slowly on sites with huge config trees. Starting with 1.0.3, the entire config tree is slurped into memory. This causes a significant speedup at the expense of collectors memory size. The database access method can be set to "lookup" in cricket-conf.pl to revert to the old behavior if collector memory footprint is of greater concern to you than its hammering on the disk. A new way of configuring Cricket has been added. It is now possible to create a file called cricket-conf.pl, which can either reside in the same directory as the grapher.cgi and collector scripts, or in /usr/local/etc. This should eliminate the need to edit grapher.cgi, and make life easier for people running Cricket on NT. The change should be backwards compatible, please file a bug on Sourceforge if it is not. A sample file is provided as cricket-conf.pl.sample. The implementation of the MAIL action tag for monitor thresholds is complete. New VarBinds added for SNMP trap monitor thresholds. Added a new monitor threshold type, quotient. Revised and extended monitor threshold documentation. Added "exact" monitor type. A new config option, show-path, was added that allows the display of the current path in the config tree with clickable links for easier backtracking. Defaults to "no". Support for SNMP version 2, as well as for the additional options that SNMP_Session now offers has been added to the sample-config/Defaults file. For existing installations, it is recommended you install the sample-config/Defaults into your cricket-config and merge your local changes in. If you do this, or if you install Cricket from scratch, you should use SNMP_Session version 0.80 or higher (it is recommended to upgrade SNMP_Session regardless, there were some rare but hard to diagnose bugs in versions prior to 0.78). The defaults are snmp-timeout = 2.0 snmp-retries = 5 snmp-version = 1 If you know what you're doing, you could reduce the number of retries or the timeout, but this is not recommended. Existing Cricket users should first evaluate the effectiveness of the dead-SNMP-host detection that's new with 1.0.3 before mucking with the SNMP settings. Support for Windows Management Instrumentation on the Windows 2000 Platform. Refer to doc/win2kwmi.html. The SNMP code now has intelligence built in to quickly skip a device that is down or otherwise doesn't respond. After two successive timeouts from the same host in one collector run, that host is considered down and subsequent targets on that device will be marked 'U' without checking. This avoids the syndrome where one device with a lot of targets can significantly slow down your collection process. If you need, you can increase MAXTRIES in lib/snmpUtils.pm. [patch #402839] The top level Defaults now sets rrd-min to 0 rather than undef. The default rrd-ds-type now is DERIVE instead of COUNTER. These changes prevent spikes in graphs. For the intrepid, Cricket can now monitor snmp-uptime in conjunction with using COUNTER (usage of snmp-uptime is not recommended unless you understand all possible causes of SNMP counter rollover; not all agent resets can be detected). [bug #209530] squid-proxy subtree Defaults file has changed. One more variable is being polled now (CurrentLRUExpiration) so you'll need to either recreate your old RRDs, or use add_ds from the RRD distribution's contrib directory to add a datasource to your existing RRD files. Some internal changes were made that should not be user visible (except perhaps by running faster in some cases). Most notably, the internal database types "D:" and "n:" are no longer generated. All files were made consistent in their indentation. We now use soft tabs exclusively, with a tab size of 4. RPN now allows negative constants, like in this magic which inverts a percentage from 0-100 to 100-0: 100,-,-1,* Fixes bugs #472862 & # 450391. Portability: Some failures on AIX have been eliminated. More hardware platforms are supported by lib/RRD/Format.pm. New features, fixes in version 1.0.2 (18-May-00): Fixed bug #104757, $clmxCode/max-color bugs. New features, fixes in version 1.0.1 (18-May-00): Various small bugs fixed, related to mixed case names. Documentation split into smaller files and reviewed. First release from Sourceforge's CVS server. Assorted bugs and patches that were also integrated: bug 212204 compile: add config-dir name to output bug 202221 Fix for display of scaled values in mtargets bug 202262 Summary should show up at bottom (optionally) [feature] bug 202266 mtargets and targets and multi-target2 [doc clarification] bug 202455 need to quote url passed to test-url bug 205504 collect-subtrees / subtree-times [feature] bug 205559 integrate functions of run-subtree into collector bug 205560 collector log rolling for .time files bug 205595 Collector optimizations bug 206305 Persistent monitoring toggle patch 400218 cricket-0.72-current_path.patch patch 400282 cricket-0.72-showmax.patch (docs changes) patch 400431 lib/RRD/Format.pm had incorrect regex for supported archname (similar to bug 206166, and a couple of others) New features, fixes in version 0.72 (23-Jan-00): Fixed a typo in one of the "fixed" split lines from the last release. Fixed a typo that makes scaled mtargets work right. Fixed the height-hint in the root Defaults file to match the height used by rrdtool-1.0.10. Fixed y-min/y-max code, hopefully once and for all. Directories named 'CVS' (exactly) in the config tree are ignored now. Collect-subtrees now makes the log directory if necessary. Minor documentation fixes to reference.html. Cleared up warnings when inst-names tag is missing. New features, fixes in version 0.71 (09-Jan-00): This release has a minimal number of patches to fix the most pressing bugs reported to me in the last 4 months. It works with RRD Tool 1.0.10. You must upgrade your RRD Tool install to this version to use Cricket 0.71. (This is because some critical bugs have been fixed recently in RRD Tool.) If this version of Crikcet proves to be stable, it will be renamed 1.0. Future development will take place in 1.0.x releases, culminating in a 1.1 release. In general, three-digit releases will be development, two digit releases production. Here are the patches that have been applied to Cricket 0.70: Minor documentation fixes. A new avg() function to go with the sum() function. Scaling works right in multitargets now. Event names are case insensitive, as documented now. Comma and colon delimited lists now ALWAYS allow whitespace. Previously, rrd-tune was broken because of a split() that was too restrictive. All of these have been fixed. Cricket no longer gives RRD Tool input that crashes it. Support for various architectures added to Format.pm. Lots of changes to Monitor.pm, but it's still not ready for production use. Sorry. http-performance tests a slightly different URL New features, fixes in version 0.70 (11-Aug-99): ATTENTION: You must use RRD Tool 1.0.x with Cricket 0.70 and higher. Cricket now defaults to making PNG pictures, if you are using a 4.0 browser or better. If you really want GIFs (which are big and slow, now that GD has been lobotomized by Dr. Unisys), then you need to investigate the graph-format tag in the graph dictionary. Frobbed the quoting magic some. I think it was broken before. I think it is less broken now. If you see odd behavior, suspect quoting problems. Integrated a patch from Shannon Reis that adds avg/max display in. This adds an additional call to RRDs::graph. If you want it the old way, check out the tag named show-avg-max in the graph dict. backed out change related to lower-casing the target names in the targets and mtargets tags. What was I thinking? usrModemUsage script no longer tries to use common.pm. removed useless and confusing reference to domain in the routers subtree. made warnings work right in util/* again. Oops. made the mapping routines use collect=false when called from the collector. Note: if you set collect to 'false', the target will still show up in the grapher and if necessary, the instance will be mapped. This could cause warnings in the grapher, but they can be ignored. New features, fixes in version 0.69 (16-Jul-99): A bunch of namespace fixes: The collector can again make directories when necessary. Instance mapping works. Lots of other fixes... Monitoring is now documented, and seems to work. Please read the description of the tag named 'monitor-thresholds' and discuss this stuff on the mailing list. Javier Muniz, who wrote it, will be available to help us learn about it in the coming weeks. The new unknown-is-zero tag in the targets dictionary makes it possible to add together graphs which have unknown regions and not have the unknown regions blot out the entire graph. New features, fixes in version 0.68 (09-Jul-99): Made it possible to use mixed case in mtargets and targets specifications. Integrated patch that makes y-min and y-max datasource-level. Added patches to make it work under NT (again). Significant changes to namespace usage to make it work under mod_perl. Search for mod_perl in reference.html for more info. Fixed sum() to work right. Fixes to make it work (sorta) under mod_perl. Fixed a minor bug related to iterating over results from the table walks done during instance mapping. Made the error message for an unmatched quote include the file and line number. New features, fixes in version 0.67 (09-Jun-99): Fix: Added quoting to the inst tag to protect the argument of map() from eval. Fix: made overzealous warning from Perl (in Log.pm) go away. Fix: A vector instance with one element is now correctly treated like a fixed-instance scalar. If you didn't understand that, don't worry. What it means is that Cricket will Do the Right Thing. Fix: missed a conversion of expandString. Instance mapping now works again. New features, fixes in version 0.66 (03-Jun-99): Fix: instance names are correctly shown in the title after visiting the instance seletor widget. New feature: target tag collect=false tells the collector to skip that target. Fix: all dictionaries are expanded with respect to the target dictionary, which seems to be more in line with how people thought it would behave. I'm not certain how people are going to use this, but if it makes people less confused, it's good, I guess. New feature: events. You can mark a graph with a vertical rule of any color to mark the time something of interest happened. Check out the new events target tag, and the event dictioanry. This requires Date::Parse, but you should have that anyway. If not, go to CPAN and get it. New feature: eval's during expansion. You can surround code you want eval'd with {} in a tag, and that will happen immediately after expansion. So this: snmp-port-2 = "%snmp-port% + 1" results in snmp-port-2 getting set to 162 (i.e. 161 + 1). Useful functions to make use of this will be coming to a release near you soon. In particular, there will be a snmpget() and a snmpwalk() that behave in ways that help make the config tree more compact. New feaute: directories can have descriptions which show up in directory selection list. See target tag directory-desc. SLIGHTLY INCOMPATIBLE CHANGE: I renamed the meta tag, since it makes more sense to call it 'head-html' where it is used. Compare your existing root Defaults file to the one in sample-config and you'll see the change. See the docs for how you might use this to use stylesheets to override my terrible sense of style. Converter completely removed. If you have RRD files in pre 0.99 format, you'll need to use Cricket 0.65 to convert them, then upgrade to later versions. The white in cricket-sm.gif is now transparent. And it's 20 bytes smaller. Cool. Added a new subtree for monitoring news servers. grapher.cgi will no longer fail on negative data. We now compute the si_unit using abs($value). Expansion is now working correctly in the target selector widget. There's a new trap subroutine in snmpUtils that makes it possible to specify OIDs in outgoing traps. New features, fixes in version 0.65 (19-May-99): This is the paper-bag-over-my-head release. This fixes a big bug in the config tree system that made it unable to understand config trees deeper than two levels. There are also some minor fixes to ignore RCS files correctly. New features, fixes in version 0.64 (18-May-99): Sorry I got a bit verbose this time... a lot of infrastructure stuff has been happening recently. Keep reading all the way... there are some incompatible changes in this release. This version was tested with RRD Tool 0.99.29.1. It should work with other 0.99.x releases of RRD Tool also. You need to have the Perl module named DB_File installed to use this release. It's relatively standard, but if you don't have it, go get it from CPAN. (See doc/beginner.txt for more info on CPAN.) You can check to see if you have it by executing: perl -MDB_File -e 'print $DB_File::VERSION, "\n"' New feature: Cricket operates off of a compiled form of the config tree, which is stored in a file named config.db in the root of your config tree. The collector will recompile this as necessary, but when you are making interactive changes to the config tree, you need to remember to recompile it yourself -- the grapher does not have the permissions it needs to recompile it. The grapher will set the variable auto-error with a warning about the out-of-date compiled form. If you have added that variable to your page-footer tag, you'll at least get a reminder message. (search for auto-error in sample-config/Default for an example). To recompile the config tree manually, run: $HOME/cricket/compile -base If your config tree is in ~/cricket-config, you can omit the -base argument, since that is the default. New feature: ds-sources can now return text information after an floating point number, and Cricket will correctly ignore the text and use only the number. See the EXEC example in the docs for how this might be useful. New feature: you can defeat the cache by hand editing the URL that points to the target display page. Add '&cache=no' onto the end of the URL. This is helpful when you are making changes to the graph dictionary, and you want to regenerate the graphs on demand to see your changes. Remember, however, that you still need to recompile the Config Tree after each change. INCOMPATIBLE CHANGE: collect-subtree is now named collect-subtrees. It reads a file named subtree-sets which is slightly different format. For one, you need to replace the "subtree foo" lines with "set foo". You also need to nae subtrees by their full path relative to the root of the config tree. For instance, "router-interfaces" becomes "/router-interfaces". INCOMPATIBLE CHANGE: the URLs used by the grapher are a bit different now. If you have saved bookmarks that jump into certain parts of the config tree, they might not work now. Check them, and fix them if necessary. (The target parameter is no longer rooted at the Unix root.) New subtrees: usr, for monitoring USR modem chassis. New utilities: generate-statics and pmlines.pm, a more flexible replacement for PM3lines. Note that the portmaster subtree may not be in sync with the utility anymore... I'm not a portmaster user, so I'm relying on patches from you guys. generate-statics is a cool little tool to take snapshots of the GIFs. Read about it in the util/README file. New feature: graph dict tag rrd-graph-args allows you to send arguments directly in to RRD Tool, which Cricket does not already send for you. A notable use of this would be to set colors on the graphs to something other than the default. See the docs for more info. I made the page-footer easier to override in subtrees. If you want to put something in the center pane replace the tag named contact with your stuff. New feature: if mtargets-ops is "sum()", it is replaced with the right number of plusses. Grapher.cgi is a big mother, and it takes a long time for Perl to parse it. It now redirects image requests through mini-graph.cgi, which is a tiny mother, which is fast to parse. This makes your webserver spend less time parsing Perl and more time spraying GIFs, which is a Good Thing. This may not work on NT. If not, comment out the line in grapher.cgi with "mini-graph.cgi" in it. (It's a substitution on the variable $me.) Please send patches to make this work on NT. :) You may need to add a new link in ~/public_html/cricket to point to mini-graph.cgi. New features, fixes in version 0.63 (02-Apr-99): INCOMPATIBLE CHANGE: If you have installed previous versions, you need to compare your targetType lines to the ones in the sample-config tree and fix them. Basically, tags are no longer allowed to appear multiple times, instead they must appear once with a comma-delimited list inside quotes. Jeff Jensen gave us the kick butt routing subtree, which lets you watch BGP updates with Cricket. The paper I wrote for NetA 99 is in doc/neta-paper. A new smaller rrdtool.gif Tobi gave me a long time ago which I forgot to add. Various bugfixes to jpj's new mtargets thing. Fixed a goofy bug which made view get set wrong when there were targets with and without a view in the same directory. collect-subtrees processes all subtree sets if it was given no arguments. Also cleaned up an error message. We now set --base correctly when drawing graphs. We decide how to set it based on the bytes tag in the graph dictionary. Unspecified bug in VRULE generation for yearly graphs fixed. Russ is the man. New features, fixes in version 0.62 (27-Mar-99): Fixed some bugs in getFormat.c that the Compaq (cough) Alpha let us notice. It now creates output suitable to cut and paste into lib/RRD/Format.pm. Yippee. atm-interfaces subtree was contributed by Alan. grapher.cgi is friendlier about a missing VERSION file. But you really _should_ still have it in the same directory that grapher.cgi is in. Added the new disable-short-desc tag, which can make more room in the target selection table by skipping the description column. Fixed the README to reflect a recursive diff for submitting patches. (jpj) Updated or created documentation for targets, targets-short-desc, targets-long-desc, mtargets, and mtargets-ops. (jpj) Implemented support for graphing multiple targets on the same graph using the mtargets tag. (jpj) Implemented basic support for graphing of arithematic operations on targets. You can now graph the sum of multiple targets, as well as other operations! (jpj) New features, fixes in version 0.61 (07-Mar-99): Interlaced now works as specified in the manual. Previously you had to use "interleaved", which is not what the docs said. You can specify the time at which a given measurement was taken. Search for XXX@YYY in the reference docs. Minor efficiency change in collector. Cricket can now send traps with the data it is collecting inside. This is useful if you happen to be running Netcool, and you've set things up correctly to escalate alerts holding data that violates certain thresholds. However, it is implemented to be flexible enough to later let us send data other places. See the docs for tag copy-to for more info. As part of the copy-to implementation, snmpUtils now knows how to send SNMP traps. Cool. New features, fixes in version 0.60 (05-Mar-99): CHANGE REQUIRING YOUR INTERVENTION!!! Old versions of Cricket might not have set rrd-min and rrd-max correctly in your RRD files. In this version of Cricket, the values come from the datasource dictionary, and from the target dictionary. If they are present in the target dictionary, they override the ones in the datasource dictionary. Check your config now to make certain it's doing what you want, and then run rrd-tune to apply the values to your existing RRD's. support for Portmasters, via an EXEC script. support for FreeBSD. removed unused call to localtime from grapher.cgi listInterfaces no longer lets colons creep into target names. rrd-dump truncates C strings correctly now. More control over multitargets with targets-desc-long and targets-desc-short. (Thanks, jpj.) Datasource descriptions are now displayed in the order they occur on the graphs. New features, fixes in version 0.59 (15-Feb-99): grapher now handles blank long-desc gracefully. grapher will attempt to put up an error message in a GIF (failed.gif) when it cannot give you a graph. Minor fix in options to handle unset $HOME better. Grapher debug level defaults to 'info' again. Multi-target supports instances now. The separator in the targets tag is now ";". This is not a backwards compatible change; if you were using targets before, you need to change it now. It's all still undocumented but it getting stable enough to document it. Fixed bug reated to inst not getting set and causing a warning. FAQ updated... keep those "dumb" questions coming! The only dumb question is one we don't document and learn from! RRD::Format now supports Linux again, and there's a nifty tool from Ed that makes it a no-brainer to port to new OS's. See the comments in RRD::Format. New features, fixes in version 0.58 (05-Feb-99): FINALLY: Support for RRD 0.99.* (tested with 0.99.7) (only the RRDs (shared library) module, at this time) NOTE: You MUST edit your RRA definitions, if you will be creating new RRD's. (Do it now before you forget!!!) You need to add the x-files-factor into the RRA definition after the consolidation function. See the comments in the root Defaults file. (New users can simply copy sample-config as usual.) Collector will do auto-conversion from old-format RRD files, for all you old-timers. It works like this; run 'collector' by hand once with the option '-convert'. Any RRD files that need to be converted will be. You can run with '-convert' as often as you want -- it will only do the conversion once. You can also convert a file on demand using util/convert-file. The old files are left in the same directory as "foo.rrd.old". Using find to remove them would save a lot of disk space -- if you trust the converter. :) The command is: find ~/cricket-data -name '*.old' -exec rm {} \; collect-subtrees now takes a -cf arg, which you can use to tell it to look at a different subtrees file. Fixed incorrect HTML entity in sample-configs/rotuers/Defaults. Cricket's version is now available in the HTML dictionary, in both it's long and short forms. You can see it in action in the newest root Defaults file, which puts the version number in the footer. New features, fixes in version 0.57 (02-Feb-99): Sample config tree now contains Cricket logo and the RRD logo. You should definately upgrade your root Defaults file... the logos look cool! Changes cached expiration system to be real-time. This means no evil cron job (yeah), no permissions problems (yeah) and no possibility of incorrectly cached graphs. I think. Expansions on the target list pages are now handled correctly. Short-desc is used now in the aggregates and at the top of target pages. New tag: bytes, which tells si_unit to calculate the prefix using powers of 2 instead of 10. "bytes" is set to true for ifInOctets and ifOutOctets in the sample tree now. Ignores files like "#foo#", which emacs can leave behind as backups. Datasources were not actually case insensitve like the docs claimed they were. Now they are. (Rumor has it they aren't everywhere yet...) Fixed bugs related to instance lists with variable expansions in them. Also a bug related to quoting these guys. Fixed bug in the way micro symbol was displayed. RRD::Format now supports arch i86pc-solaris and MSWin32-x86. listInterface will quote more lines that need it (i.e. empty or whitespace only lines). New features, fixes in version 0.56 (15-Jan-99): Documentation updated in various ways, including new features, new info on purging the GIF cache, and fixed typos. Made RRD::Format cross-architecture. Supports sun4-solaris and i386-linux. For tips on porting to other archtectures, see lib/RRD/Format.pm. Reworked Makefile to make a more better tarfile (writeable files, and no garbage in lib). Added a "rand" parameter to image URLs so that Netscape will let RRD decide what things should be cached. Search for "rand" in grapher.cgi to shut this off, if you are annoyed by the slightly longer load times. Added a caching mechanism to the graph drawing routine. The cache dir can be changed easily by editing the beginning of grapher.cgi. It will never hold more than 5 minutes worth of graphs, so /tmp should be appropriate. Clearing the cache is done by running grapher.cgi from cron with certain options. Search for "cache" in the documentation. Made collect-subtree strip leading whitespace from subtree names correctly. Taught listInterfaces and router-interfaces subtree about sub-interfaces. It turns out that some interfaces, like those related to PVC's on frame relay circuits, will not tell you the packet or error counts. So listInterfaces tries to mark those as "sub-interface" type interfaces, so that we don't try to fetch things that we can't get. New feature: inst-names will let you name the instances in a vector instance list. See documentation for details. New feature: the graph dict tag si-units=false will let you disable si_unit(), which turns 2000 in 2k. The scales of the graph will still have the SI transformation done to them. The instance selector widget is now formatted as a 5 column table. New features, fixes in version 0.55 (07-Jan-99): Various fixes to make it work better on NT. Documentation expanded. listInterfaces handles quoting right. Squid OIDs revisited. (Still might not be right...) Changed the layout of the time-navigation links some. Other minor bugfixes to the grapher and collector. New features, fixes in version 0.54 (21-Dec-98): Made collect-subtree work right when there are two or more different users running it on the same machine. Also made it scan logs for errors. Made the collector more robust when making new RRD files. It reports useful stats again (# targets, amount of wall clock time). Fixed a stupid bug in RPN.pm that made it impossible to divide. Made grapher.cgi work with installs where RRD.pm needs to be installed in the local lib directory for some reason. Fixed bug in graphParam. Layed out datasources more nicely in HTML summary. Made regexp-based instance mapping work. Included an interesting example of how to use this feature. See sample-config/Defaults, and search for ^map. Added new switches subtree, which polls interesting things from a switch. This is relatively untested -- we use it here, but not in the exact form I am shipping. Please try it out and give me feedback. Resolved some questions in the docs. Added a new file datasource type. See docs for more info. New features, fixes in version 0.53 (10-Dec-98): Fixed a bug in collector regarding creating new RRD files with more than one DS. This bug has been in since 0.50. Worked on the documentation some more, including more information about the graph dictionary. New features, fixes in version 0.52 (10-Dec-98): SMRTG is now named Cricket. The website changed. There's a configure script that will take care of you if your Perl is not where mine is. Once RRD tool supports the -i (--interlaced) feature (a patch has been sent to Tobias) you will be able to use the "interlaced" attribute of the "graph --default--" dictionary to control the encoding of the GIF. New features, fixes in version 0.51 (8-Dec-98): fixes to the squid-proxy subtree (already!) fixes to the http-performance tree actually implemented locking in collect-subtrees grapher.cgi: new feature lets you set the upper and lower bounds on graphs which have occasional spikes that you don't want to see. Set "y-max" to (for example) 3.0 to trim peaks higher than 3.0. Setting y-max or y-min will disable auto-scaling, so only use it when you mean it! New features, fixes in version 0.50 (4-Dec-98): sample-config: squid-proxy subtree added all RRD users converted to use RRD module instead of talking over pipe. more complete documentation New features, fixes in version 0.49 (21-Nov-98): sample-config lots of things added to show off various features grapher.cgi: new 'precision' attribute of graph dictionary lets you control the precision of the HTML summary for that datasource. Set it to "integer" for things which should be rounded to the nearest integer (like the number of modems in use). new "space" tag in graph dict that will let you override the space printed before units. (Useful for things like the degree mark.) case-sensitivity bugfixes related to datasource descriptions. New features, fixes in version 0.48 (12-Nov-98): util/listInterfaces Now outputs ready-to-use SMRTG config files. (Thanks David!) util/ciscoDiscover An interesting contribution from Ed Bugg, which isn't quite working for me yet. limited multi-target support: if you have a target with a "targets" attribute in it, it is considered a multi-target. No data is collected for it, and when it is displayed, the grapher puts each of the graphs up one after another. grapher.cgi: scales and values in HTML and add prefixes to your units. shows fewer graphs by default, but gives you choices about which to show one you are looking at a graph. collector: easier to mess with the logging level. Use "-loglevel debug" to debug your config. Other options are 'info', 'warn', and 'error'. New features, fixes in version 0.47 (8-Nov-98): THANKS: added a place to keep track of the help people are giving me... you guys make this process work! Keep it up! collect-subtrees: a replacement for the crufty old wrap-collector script. This understands the concept of subtree-sets, which makes it much easier to parallelize SMRTG (which you need to do if you are trying to poll thousands of targets). sample-config: new http-performance subtree which makes if possible for SMRTG to monitor webservers (and FTP servers too!) documentation: a few more tidbits, still need volumes more a tiny fix to the README -- the crontab entry was missing 55! collector: a bugfix related to making new RRD's grapher.cgi: now works on Netscape's webserver too! New features, fixes in version 0.46 (4-Nov-98): collector handles oids right, for real this time. (really!) common: handleTarget is abstracted better to make adding new commands (like rrd-tune, see below) easier. grapher.cgi: pays attention to the size hints now (fixed bug) new long-desc and short-desc attributes of targets let you add text to the web pages describing the targets. new desc attribute for a datasource causes a description of the datasources to be added to the end of the graph page. we now use RRD::File to dig out the current values for the graphed variables. new attribute in graph dict called "unit". Uses this as the first choice in the summary -- will fall back to y-axis. See sample-config/routers/Defaults for an example. There is a new tool in util called rrd-tune. It traverses a config tree and tunes each RRD to the current settings for min, max, xff, and hearbeat. (Takes same args as collector). Run this on a target (or it's whole subtree, for now) after making a change to it's config. New features, fixes in version 0.45 (30-Oct-98): Miniscule bit of Documentation! ConfigTree.pm more consisten --default-- behavior: works right across all TagValue items handles missing quotes (and other errors) slightly better now possible to nuke a key that you inherited grapher.cgi no longer uses path_info, since this doesn't seem to work with all webservers. (Thanks to David Koski for his help diagnosing this.) collector handles raw oids in datasources correctly now fails gracefully in many more situations where the config tree is missing data This file started with version 0.45. cricket-1.0.5/COPYING0100644000114300000000000004310207047764266013164 0ustar bertdwheel GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1990, 1991, 1992 Free Software Foundation, Inc. 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. cricket-1.0.5/DEV-INFO0100644000114300000000000000700207370665340013212 0ustar bertdwheelInformation for developers ========================== This file is of interest to all people who will ever make or suggest a change to Cricket. A lot of time is spent on maintaining Cricket, and following the guidelines given here will result in more time being spent on Cricket and less time on the mechanics of maintenance. Even if not everything applies to you, please at least skim over it. Coding guidelines ----------------- - No hard tabs. - virtual tabs at every four spaces. - "K&R" style braces, e.g. if ($foo) { &bar($foo); } else { &baz($foo); } - No trailing whitespace allowed. - No CRLF's as line terminators allowed. - Code to fit in 80 column lines where possible. - Indentation of split lines loosely following the default Emacs style, e.g. Common::VeryLongFunctionWithLotsOfArgs($foo, $bar, "abdefg", "defghij"); I'd like to stress the "loosely", as sometimes Emacs comes out quite ugly, or guesses wrong from time to time. The Emacs magic appended to each file is: # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: Of course, you are free to use any editor you like (and matter of fact, if you're using Emacs, you'd better look out for its quirks). Rationale: most of this is the old "When In Rome, Do As The Romans Do" paradigm. Guidelines for sending diffs ---------------------------- If you want to send a diff to the mailing list or to the Sourceforge bug or patch tracker, please keep this in mind: - Plain diff is unsafe to use. Send "diff -c2" output, or if that results in an ugly diff, "diff -u" format (and if you elect to use "-u", remember that not everyone will be able to apply your patch). - Try to diff against a reliable source. "cvs diff" is preferred, but a good alternative is to unpack an unmodified copy of Cricket in ../orig/cricket-x.x.x, so that you can use diff -rc2 ../orig/cricket-1.0.2 . to produce a complete diff - Do not include unnecessary comments like your initials in the code. CVS does a great job of recording who changed what, and the developers have a good track record of acknowledging contributors. And explaining Perl syntax, that's what the Perl books are for (unless the issue really is counterintuitive, but that's pretty rare). - Always mention what the diff does, what circumstances may be relevant, and what version of the code it applies to. Guidelines for CVS commits -------------------------- When committing code to CVS, please follow these guidelines: - Always run a "cvs diff|more" before you commit, and make sure your commit does not commit stuff you didn't intend to commit. If necessary, make notes of the diff output so that you know what to put in the log message. - Avoid committing unrelated changes, if at all possible. If necessary, commit in two passes (for example, white space policing first, then functionality). - Give a meaningful log message. When committing multiple files, tell what you did to each file and why. Try to indicate even small changes like whitespace policing or fixing typos. Rationale: The commit messages are important later on, so keep the person who has to track problems later in mind when you commit. Nothing is more disrupting than having to run "cvs diff" on each revision to find out which commit introduced certain code or wording, especially if the log message seems to indicate this file wasn't affected at all by the commit. cricket-1.0.5/README0100644000114300000000000000645510031605175012777 0ustar bertdwheelREADME for Cricket version 1.0.5 ================================ This is Cricket. It is a configuration, polling, and data-display engine wrapped around the RRD tool by Tobias Oetiker. There are three user-visible pieces to Cricket: the collector, the grapher, and the config tree. The collector runs from cron and fetches data from a number of devices according to the info it finds in the config tree. The grapher is a CGI application that allows users to traverse the config tree from a web browser and see the data that the collector recorded. To use Cricket you need to do these things: * make a config tree * setup the collector to run on a regular basis * setup the grapher to let you look at the data Installing ---------- If you are a brand new Cricket user, you should read and closely follow the directions in doc/beginner.html. Upgrading ---------- (1) Read the CHANGES file to see what's new with this version. (2) Install this version to a separate directory from your existing version. (3) Copy subtree-sets and cricket-conf.pl from the existing version to this installation. Alternatively, rename this version cricket-conf.pl.sample to cricket-conf.pl and edit the file to match your settings from the cricket-conf.pl existing version. (4) Other potential pitfalls: If you use subtree timing info, you will need to edit collect-subtrees to set $subtreeTimes = 1. If you have placed custom images in cricket/images, you will need to copy these files from the existing version to this installation. (5) Cutover to this installation from your existing one by swapping the $HOME/cricket softlink to this version. (6) Recompile your config tree. (7) Run the collector or collect-subtrees by hand once to make certain it's still working. (8) Now you are ready to start experimenting with the new features described in the CHANGES file. Documentation ------------- Point your web browser at the doc/index.html file and choose the most appropriate document from there. Patches ------- If you manage to add a useful feature to the code (or need to make a bugfix just to get it running) please share a patch with the Cricket community, or consider becoming a Cricket developer and adding your code in to the next release. You can generate a patch like this: tar xvf cricket.tar mv cricket my-changes # make your changes to this this copy tar xvf cricket.tar gnudiff -u -r cricket my-changes This will create a unified context diff from the dist directory to the "my-changes" directory. It will allow people to apply your changes to their copies of Cricket. You can find out more about contributing to Cricket development here: http://cricket.sourceforge.net/devel Support ------- You can find the most recent version of Cricket at: http://cricket.sourceforge.net/ There is information about how to get help with Cricket online here: http://cricket.sourceforge.net/support/ At that website, you can find the FAQ, mailing lists to join, mailing list archives to search, and other useful information about how other people are using Cricket. License ------- Cricket is covered by the GNU General Public License. See the file COPYING for copying permission. Cricket is Copyright (c) 1998-2004 by Jeff Allen Individual pieces are copyrighted by their contributors, though they are covered by same license as Cricket, namely the GPL. cricket-1.0.5/THANKS0100644000114300000000000001044607775401054013040 0ustar bertdwheelThanks to Tobias Oetiker, the author of RRD, for his very useful tool. Without RRD, Cricket would be useless. (And without Cricket, RRD is still cool, but hard to use!) Tobias also contributed the cool Cricket logo. Thanks to WebTV Networks for letting me work on this tool. If it's half as useful to others as it is to us at WebTV, we've definitely made the network management world a better place! I can only justify spending time on this project as long as WebTV gets new stuff for free in return. So I am indebted to these contributors for making this project possible. These folks contributed ideas, patches, and/or debugging info: Jase MacLeod Identified and fixed a threshold monitor bug in 1.0.4 Francois Mikus Monitoring enhancements Terje Bless Website updates Patch review and integration for 1.0.4 Lars Hansson Spotted a bug in mungeDsName John Woolner Made getFormat.c portable to DEC Alpha Albert Herranz Martin Provided the solution for instance names that look like floats to Perl Clifton Royston updated listInterfaces with boatloads of new functionality Jeremy Hinton contributed the "NaN in pinkish background" feature identified and fixed a number of issues in the 1.0.4-pre1 prerelease Nils Ketelsen and Raffael D'Albenzio were instrumental in squashing the mtargets/mtargets-ops bugs relating to mixed case targets and directories Greg Whitlock finally convinced Bert that there was a bug in the treatment of "bytes=false" Taso Devetzis monitor type "exact" Jake Brutlag monitoring/monitoring documentation WMI on Win2K support Adam Meltzer perfmon on Win2K support sql provider memory slurp additions bug fixes Chris Adams made show-max and scaling like each other Matt Zimmerman handles Debian lots of work on the mailing lists James Grinter many contributions, including championing the self_url issues and lots of work on the mailing lists Mike Fisher contributed the dead device detection Bert Driehuis pushed to get DERIVE the distributed standard contributed snmp-uptime various minor bugfixes Shannon Reis contributed a fix for annoying y-min/y-max behavior contributed the nifty avg/max reporting, and added max line to graphs. Jost Krieger contributed the news-server subtree Matthew Stier Various small bugfixes. Jeremy Fischer contributed usr subtree Noam Freedman contributed generate-statics Robert J. Adams submitted the pmlines script for monitoring portmasters. S. John Banner found the right format strings for AIX Juan Cabezas S. found the right format strings for Irix Russ Wright Fixed a bug in VRULE generation for yearly graphs. Ragnar Kjrstad The squid-proxy configuration. Blair Zajac Changes to use shared-object RRD module Taught me how to use return. :) Patrick Myers Found correct settings for FreeBSD. Philippe.Simonet@swisscom.com Continually re-porting to NT as I break it. Also, game me the func scheme for ds-sources. Simon Leinen SNMP in Perl 5 Alan Lichty Found a nasty bug related to corrupted instances. Applying Cricket to Cascade ATM switches (the atm-interfaces subtree). Ed Bugg contributed the converter and getFormat Matija Grabnar Verified that Linux formats work on i86pc-solaris. David Koski auto-configuration via listInterfaces SNMP v2 support Javier Muniz Monitoring bug finder Kirby Krueger test-url script maintenance Bryce Jasmer expansion bug catcher Jeff Jensen config tree brainstorming meanest beta tester alive usrModemUsage tool and modems subtree switches subtree, as well as the routing subtree nifty multi-target support unknown-is-zero Emmett Hogan a prototype of SMRTG that crystalized our ideas about the config tree the contributors site cricket-1.0.5/TODO0100644000114300000000000000252707047764266012627 0ustar bertdwheelCollector: focus does not work right for directories not in top level focus does not work with an extra / on the dir name Grapher: remove legend from GIFs think about fetching values in response to %fetch(ds)% syntax Cricket Paper DONE: outline ready for review DONE: outline sent in DONE: paper accepted! write paper Later: figure out what's wrong with expires, decide on refresh same-as tag for target-types, so that jpj can make a Cisco-7500-Router same-as a Cisco-7200-Router. (proper handling of --default-- helps obviate this need) Search for ERR in grapher.cgi and implement reasonable error notification. Make it possible to add the MAX data to graphs. (MRTG 2 does them only for old graphs... hmm.) Add loop detection in expand* routines. Make more things configurable via the HTML dict, to aid i18n. (i.e. instead of hardcoding "Values at last update", get it from the dict, if possible) Add an include syntax. (Useful for i18n, and for building libraries of target-types for novice users.) make it possible for subtree sets to include other subtree sets. figure out why colons are not allowed in target names and decide what to do about it (currently, it's just documented). There's a Perl module named FastTemplate that might make the HTML display significantly more powerful. Unify the repeated code between collector and rrd-tune. cricket-1.0.5/VERSION0100644000114300000000000000004310031605175013152 0ustar bertdwheelCricket version 1.0.5 (2004-03-28) cricket-1.0.5/collect-subtrees0100755000114300000000000001714407365642757015350 0ustar bertdwheel#!/usr/local/bin/perl -w # -*- perl -*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # This is a smart wrapper for collector. # It understands the concept of "subtree sets". It takes one # argument, the subtree set it will process. It rolls the logfiles # for that set, then runs the collector on that subtree set. # it reads it's subtree sets from a config file, by default a file # in the cricket install directory named 'subtree-sets'. You can # and should put the file somewhere else, and use the -cf argument # to tell collect-subtrees where to find it. BEGIN { my $programdir = (($0 =~ m:^(.*/):)[0] || "./") . "."; eval "require '$programdir/cricket-conf.pl'"; eval "require '/usr/local/etc/cricket-conf.pl'" unless $Common::global::gInstallRoot; $Common::global::gInstallRoot ||= $programdir; } $| = 1; # Useful if you want to tail -f a logfile while the collector runs! :) use lib "$Common::global::gInstallRoot/lib"; use Getopt::Long; use Common::global; use strict; my @mHist; my $gMailTo; my $gDebug = 0; my $gCF = "$Common::global::gInstallRoot/subtree-sets"; GetOptions( "cf=s" => \$gCF, "mail|m=s" => \$gMailTo, "debug|d" => \$gDebug, ); my $gLogDir = "$Common::global::gCricketHome/log"; my $gBase = $Common::global::gConfigRoot; my $gKeepLogs = 20; my $gUser = getlogin || getpwuid($<); # idiom from perlfunc manpage my $gCurSet; my $mOut = 0; $mOut = 1 if(defined($gMailTo)); my ($gSets, %gSets, @gSets); # Change this to = 1 if you want to get collection length statistics. my $subtreeTimes = 0; # Change this to your local mailer if you don't have /bin/mailx! my $gMailer = '/bin/mailx'; open(S, "<$gCF") || die("Could not read $gCF file.\n"); while () { chomp; # nuke comments and blank lines s/^\s*#.*$//; next if (/^\s*$/); if (/^\s*set\s+(.*):/i) { $gCurSet = $1; next; } elsif (/^\s*base:\s+(.*)/i) { $gBase = $1; if ($gBase !~ m#^/#) { $gBase = "$Common::global::gCricketHome/$gBase"; } } elsif (/^\s*logdir:\s+(.*)/i) { $gLogDir = $1; if ($gLogDir !~ m#^/#) { $gLogDir = "$Common::global::gCricketHome/$gLogDir"; } } else { my($subtree) = $_; $subtree =~ s/^\s*//; if (! defined($gCurSet)) { warn("No set lines found. Ignoring subtree $subtree.\n"); } else { push @{$gSets{$gCurSet}}, $subtree; } } } close(S); # if none specified, do them all if ($#ARGV == -1) { @ARGV = sort(keys(%gSets)); } foreach my $s (@ARGV) { my($subtreeRef) = $gSets{$s}; my(@output) = (); my($debugFlag) = ""; if (! defined($subtreeRef)) { if($mOut == 1) { push @mHist, "$0: unknown subtree name: $s\n"; } else { print STDERR "$0: unknown subtree name: $s\n"; } next; } my($lockfile) = "/tmp/$gUser-subtree-$s"; # file locking relies on touch, which is not available on Win32 if (!isWin32()) { if (-f $lockfile) { if($mOut == 1) { push @mHist, "Subtree $s is currently being processed." . " If this is a mistake,\n"; push @mHist, "use \"rm $lockfile\" to unlock it.\n"; } else { print STDERR "Subtree $s is currently being processed." . " If this is a mistake,\n"; print STDERR "use \"rm $lockfile\" to unlock it.\n"; } next; } # lock it system("touch $lockfile"); } rollLogs($s); my $logfile = "$gLogDir/$s.0"; my $timelogfile = "$gLogDir/$s.times"; if(-f $timelogfile && -s $timelogfile >= 50000 && $subtreeTimes == 1) { rename $timelogfile, "$timelogfile.old"; } $debugFlag = "-loglevel debug" if ($gDebug == 1); my $args = "$debugFlag -base $gBase " . join(" ", @{$subtreeRef}); # Win32 does not support #! notation system((isWin32() ? "perl " : "") . "$Common::global::gInstallRoot/collector $args > $logfile 2>&1"); # unlock it unlink($lockfile) if (!isWin32()); # scan the logfile for errors and send them to stderr # so that cron sends them to the admin. if (open(L, "<$logfile")) { (open(TL, ">>$timelogfile") || print STDERR "Couldn't open $timelogfile: $!") unless (!$subtreeTimes == 1); my $lastWasError; my $skipLine; my (@hist, @mHist) = (); while () { # keep 5 lines of history push @hist, $_; shift @hist if ($#hist+1 > 5); # errors are marked with a * after the date: # [21-Dec-1998 15:57:54*] RRD error ... if (/\*\] /) { if ($lastWasError) { if($mOut == 1) { push @mHist, $_; } else { print STDERR $_; } } else { if($mOut == 1) { push @mHist, "\nError and the lines leading up to it:\n"; push @mHist, @hist; } else { print STDERR "\nError and the lines leading up to it:\n"; print STDERR @hist; } } $lastWasError = 1; } else { if ($skipLine) { if($mOut == 1) { push @mHist, "\n"; } else { print STDERR "\n"; } $skipLine = 0; } $lastWasError = 0; } } close(L); if ($subtreeTimes == 1) { print TL $hist[$#hist]; close(TL); } if(defined($mOut)) { mailHist($s, @mHist); } } else { warn("Could not scan logfile $logfile for errors: $!\n"); } } sub rollLogs { my($logName) = @_; my($i) = $gKeepLogs; if (! -d $gLogDir ) { MkDir($gLogDir); } while ($i > 0) { rename("$gLogDir/$logName." . ($i-1), "$gLogDir/$logName." . $i); $i--; } } sub MkDir { my($dir) = @_; if (isWin32()) { my ($dd) = $dir; $dd =~ s/\//\\/g; `cmd /X /C mkdir $dd`; } else { system("/bin/mkdir -p $dir"); } } sub isWin32 { return ($^O eq 'MSWin32'); } sub mailHist { my ($subtree, @output) = @_; if($#output > 0) { my $subject = "output from subtree " . $subtree; open(MAIL, "|$gMailer -s \"$subject\" $gMailTo") || die "Couldn't run $gMailer to send subtree output: $!\n"; print MAIL @output; close(MAIL); } } exit 0; # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/collector0100755000114300000000000004577307750606506014056 0ustar bertdwheel#!/usr/local/bin/perl -w # -*- perl -*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. BEGIN { my $programdir = (($0 =~ m:^(.*/):)[0] || "./") . "."; eval "require '$programdir/cricket-conf.pl'"; eval "require '/usr/local/etc/cricket-conf.pl'" unless $Common::global::gInstallRoot; $Common::global::gInstallRoot ||= $programdir; } use lib "$Common::global::gInstallRoot/lib"; use RRDs 1.000101; use Common::Version; use Common::global; use Common::Log; use Common::Options; use Common::Util; use Common::Map; use Common::HandleTarget; use ConfigTree::Cache; use Monitor; # here's where the individual datasource routines live use snmp; use exec; use file; use field; eval "use sql"; if (Common::Util::isWin32()) { eval "use wbem"; eval "use perfmon"; } my $starttime = time(); # See the documentation on ds-source FUNC for why this defaults # to commented out. # use func; Common::Options::commonOptions( "skipMonitor" => \$Common::global::gSkipMonitor); Info("Starting collector: $Common::global::gVersion"); $gTargetCt = 0; $Common::global::isCollector = 1; $Common::global::gCT ||= new ConfigTree::Cache; $gCT = $Common::global::gCT; $gCT->Base($Common::global::gConfigRoot); $gCT->Warn(\&Warn); if (! $gCT->init()) { Die("Failed to open compiled config tree from " . "$Common::global::gConfigRoot/config.db: $!"); } my($recomp, $why) = $gCT->needsRecompile(); if ($recomp) { Warn("Config tree needs to be recompiled: $why"); system( (Common::Util::isWin32() ? 'perl ' : '') . "$Common::global::gInstallRoot/compile " . "-base $Common::global::gConfigRoot"); $gCT = new ConfigTree::Cache; $gCT->Base($Common::global::gConfigRoot); if (! $gCT->init()) { Die("Failed to open compiled config tree from " . "$Common::global::gConfigRoot/config.db: $!"); } } &snmpUtils::init(); # if they gave us no subtrees to focus on, use the root of the config tree if ($#ARGV+1 == 0) { push @ARGV, '/'; } # foreach subtree to do # find the base node of that subtree # foreach leaf node of this subtree # process it my($subtree); foreach $subtree (@ARGV) { if ($gCT->nodeExists($subtree)) { $gCT->visitLeafs($subtree, \&handleTarget, \&handleTargetInstance, \&localHandleTargetInstance); } else { Warn("Unknown subtree $subtree."); } } if (!$Common::global::gSkipMonitor) { foreach $subtree (@ARGV) { if ($gCT->nodeExists($subtree)) { $gCT->visitLeafs($subtree, \&handleTarget, \&checkTargetInstance); } else { Warn("Unknown subtree $subtree."); } } } # print some summary stuff (number of targets, time taken) # before exiting. my($time) = runTime($starttime); Info("Processed $gTargetCt targets in $time."); exit; # only use strict for the subroutines use strict; sub localHandleTargetInstance { my($name, $target) = @_; my($tname) = $target->{'auto-target-name'}; my($tpath) = $target->{'auto-target-path'}; # first, dump the dict, to help debug things if (isDebug()) { my($k, $v, $t); $t = "target $tname\n"; foreach $k (sort (keys(%{$target}))) { next if ($k eq "auto-target-name"); $v = $target->{$k}; $v = "[undef]" if (! defined($v)); $t .= "\t$k = $v\n"; } Debug($t); } # skip this if it's a meta-target if ((defined($target->{'targets'})) || (defined($target->{'mtargets'}))) { Debug("Skipping meta target $tname"); return; } if (defined($target->{'collect'}) && isFalse($target->{'collect'})) { Debug("Skipping target $tname due to collect=false tag."); return; } $main::gTargetCt++; my($datafile) = $target->{'rrd-datafile'}; if (!defined($datafile)) { Warn("Could not find a datafile for $tname."); return; } if (! -f $datafile) { return unless newRRD($name, $target); } my $agent_restart = 0; my(@data) = retrieveData($name, $target, \$agent_restart); if ($#data+1 == 0) { Warn("No data retrieved. Skipping RRD update."); return; } # look for date strings, suck em out. my($data, $when, @data2); foreach $data (@data) { if ($data =~ /@(\d+)/) { my($when2) = $1; if (defined($when) && ($when ne $when2)) { Warn("Found inconsistent times in retrieved data. " . "Using first one seen."); } else { $when = $when2; } $data =~ s/\@${when2}//; } push @data2, $data; } if (! defined($when)) { # if they didn't tell us when, use RRD's "now" syntax. $when = "N"; } # If an SNMP agent restart occurred, insert a dummy record consisting # of all "U" one second before the current sample. This causes RRD # to set the previous value of counters to undefined, avoiding weird # results when a counter goes negative because of a restart. if ($agent_restart && $when eq "N") { my @dummyresults = @data2; grep { $_ = 'U'} @dummyresults; Info("Inserting dummy record because of agent restart"); my $now = time(); RRDs::update($datafile, join(":", $now - 1, @dummyresults)); } RRDs::update($datafile, join(":", $when, @data2)); if (my $error = RRDs::error()) { Warn("Cannot update $datafile: $error\n"); } # if we were asked to copy this to someplace, go for it. if (defined($target->{'copy-to'})) { my($copyto) = $target->{'copy-to'}; my($to, $args) = split(/:/, $copyto, 2); $to = lc($to); if ($to eq "trap") { # In this case, args is expected to have SNMP info in # it. Example: trap:public@nms-101 # We just pass it straight thru to snmptrap. # This number lets our NMS identify this as a Cricket # data trap. The enterprise for the trap # is hardcoded in snmpUtils::trap. my($specific) = 3; snmpUtils::trap($args, $specific, "/$tpath/$tname", @data2); } elsif ($to eq "sql") { sqlUtils::sendto($args, @data2); } else { Warn("Unknown copy-to type: $to. Ignoring."); } } } sub handleMultiTarget { # the collector completely ignores this -- it's only # used by the grapher. } sub retrieveData { my($name, $target, $restart_ref) = @_; my($tname) = $target->{'auto-target-name'}; my($inst) = $target->{'inst'}; if (defined($inst)) { $tname .= " ($inst)"; } my($ttype) = lc($target->{'target-type'}); my($ttRef) = $main::gCT->configHash($name, 'targettype', $ttype, $target); if (! $ttRef) { Warn("Unknown target type: $ttype."); return; } Debug("Retrieving data for target $tname ($ttype)"); # Determine the list of data sources for this target. my(@targetDSs) = split(/\s*,\s*/, $ttRef->{'ds'}); if (!@targetDSs) { Warn("Could not find any datasources for target type $ttype."); return (); } # take the count before we start dicking with it # below... my($dsCount) = $#targetDSs + 1; # if we need to fetch a key to verify a cached instance, # we append it to targetDSs here. my($mapkey, $mapRef, $match, $snmp, $baseOID, $oid); if ($target->{'--verify-mapkey--'}) { $mapkey = $target->{'--verify-mapkey--'}; $mapRef = $main::gCT->configHash($name, 'map', $mapkey, $target); if (defined($mapRef)) { $match = $mapRef->{'match'}; if (defined($match)) { $match = ConfigTree::Cache::expandString($match, $target, \&Warn); } $baseOID = $mapRef->{'base-oid'}; my($oidMap) = $main::gCT->configHash($name, 'oid', undef, $target); $oid = mapOid($oidMap, $baseOID); } else { Warn("Unknown mapkey. This should not happen."); } $snmp = $target->{'snmp'}; if (!defined($match) || !defined($snmp) || !defined($oid)) { Warn("Data needed to verify $mapkey is missing. " . "Skipping verification."); delete($target->{'--verify-mapkey--'}); } else { my($inst) = $target->{'inst'}; my($newds) = "--snmp://${snmp}/${oid}.${inst}"; push @targetDSs, $newds; } } if (defined($target->{'snmp-uptime'})) { my $ds = "--snmp://$target->{'snmp'}/$target->{'snmp-uptime'}"; push @targetDSs, $ds; } # this will hold a hash of ds-method names. the values will # be a ref to a list of the sources that get passed to # the ds-method later. my(%targetDataSources) = (); my($dsIndex) = 0; my($ds); foreach $ds ( @targetDSs ) { my($dataSource); if ($ds =~ /^--/) { # this is a hacked one, from the verify code, above. $dataSource = $ds; $dataSource =~ s/^--//; } else { # this is a normal ds, so go look it up. my($dsRef) = $main::gCT->configHash($name, 'datasource', lc($ds), $target); if ($dsRef) { # computed data sources need to be excluded fetch if ($dsRef->{'rrd-ds-type'} eq 'COMPUTE') { $dsCount--; # don't penalize for missing these DS next; } $dataSource = $dsRef->{'ds-source'}; $dataSource = ConfigTree::Cache::expandString($dataSource, $target, \&Warn); } else { Warn("Datasource named $ds not found."); next; } } my($dsMethod,$dsLine) = split(':', $dataSource, 2); # create a hash entry which is an array of datasources # of the same TYPE (i.e. snmp, shell, etc). # NOTE: The datasource type is REPLACED by the datasource # index. This is so that we can be sure to reassemble the # data source return value array in the right order. push(@{ $targetDataSources{lc($dsMethod)} }, "$dsIndex:$dsLine"); Debug("ds$dsIndex is: $dsLine"); $dsIndex++; } # For each different data source type (snmp, exec, etc.) # call the fetcher to retrieve the data. my($dsList, $type, @mixedResults); while (($type, $dsList) = each %targetDataSources) { if (defined ($main::gDSFetch{$type})) { push(@mixedResults, map { $_ = $type . ":" . $_ } &{$main::gDSFetch{$type}}($dsList, $name, $target)); } else { Warn("Could not find a fetcher with type $type to " . "fetch data for $tname."); } } # Reassemble the data in the right order. my($line, @results); foreach $line (@mixedResults) { my($type, $index, $value) = split(/:/, $line, 3); # only take the first token from the line... that # way, they can put in-line comments in the returned data. # do this only for 'exec' and 'file' data sources if (lc($type) ne 'snmp') { $value =~ s/^\s*//; ($value) = split(/\s+/, $value, 2); } $results[$index] = $value; } # Make sure there are no gaps in the return data! # If any data is missing, make it undefined ("U") my($ctr, $missingData) = (0, 0); for $ctr (0 .. $#results) { if (! defined $results[$ctr]) { $results[$ctr] = "U"; $missingData = 1; } else { if ($results[$ctr] eq 'U') { $missingData = 1; } } } # Check the agent uptime with the poll interval. If the uptime is # less then one poll interval, notify our caller of the restart. if (defined($target->{'snmp-uptime'})) { my($agent_uptime) = pop @results; my($poll) = $target->{'rrd-poll-interval'}; $poll = 300 unless (defined($poll)); if ($agent_uptime ne "U" && $agent_uptime < $poll * 100) { Info("Agent uptime is less than poll interval"); $$restart_ref = 1 if (defined($restart_ref)); } } # if we are verifying, check the # fetched mapping key to make certain it's right if (defined($target->{'--verify-mapkey--'})) { my($fetchedKey) = pop @results; my($wrongInst); if ($match =~ /^\s*\/(.*)\/\s*$/) { $match = $1; $wrongInst = ($fetchedKey !~ /$match/i); } else { $wrongInst = ($fetchedKey ne $match); } if ($wrongInst) { # damn, they didn't match. this means we need to # fix the instance number using mapInstance, and # retry. Common::Map::mapInstance($name, $target); if (defined($target->{'inst'})) { # now that we have the correct inst, fetch again # (this time there is no need to verify) delete($target->{'--verify-mapkey--'}); @results = retrieveData($name, $target, undef); } else { # fill in all unknown, since the mapping key seems # to no longer exist @results = (); for ($ctr = 0; $ctr < $dsCount; $ctr++) { push @results, "U"; } } } } # Make sure that we have the same number of return values # as datasources. my($numRes) = $#results+1; if ( $numRes != $dsCount ) { Warn("$dsCount datasources required, $numRes results returned!"); @results = (); } if ($Common::global::gLogFullPath) { my $tpath = $target->{'auto-target-path'}; Info("Retrieved data for $tpath/$tname: ", join(",", @results)); Info("Some data is missing for $tpath/$tname.") if ($missingData); } else { Info("Retrieved data for $tname: ", join(",", @results)); Info("Some data is missing for $tname.") if ($missingData); } return @results; } sub newRRD { # Create a new RRD file base on the contents of the # referenced dictionary. my($name, $target) = @_; my($datafile) = $target->{'rrd-datafile'}; my($tname) = $target->{'auto-target-name'}; my($ttype) = lc($target->{'target-type'}); # Create the data directory if it does not exist: $datafile =~ /(.*)\/.*$/; my($dataDir) = $1; if (! -d $dataDir) { Common::Util::MkDir($dataDir); } # Start rrd one week ago. my($week) = (60 * 60 * 24 * 7); my($start) = time - $week - 1; my($poll) = $target->{'rrd-poll-interval'}; $poll = 300 unless (defined($poll)); my(@arg) = ($datafile, "--start", $start, "--step", $poll); my($type, $val); my($valid) = 0; my(@dsDesc) = (); my(@rraDesc) = (); my($dsCnt) = 0; my($ttRef) = $main::gCT->configHash($name, 'targettype', $ttype, $target); if (! $ttRef) { Warn("Unknown target type: $ttype."); return; } # first, process the ds tag my($dses) = $ttRef->{'ds'}; if (! $dses) { Warn("No ds tag found for target type $ttype."); return; } my($ds); foreach $ds (split(/\s*,\s*/, $dses)) { my($dsname) = lc($ds); my($d) = $main::gCT->configHash($name, 'datasource', $dsname, $target); if (defined($d)) { my($dst) = $d->{'rrd-ds-type'}; $dst = "GAUGE" unless (defined($dst)); $dst = uc($dst); my($hb) = $d->{'rrd-heartbeat'}; $hb = $target->{'rrd-heartbeat'} if (defined($target->{'rrd-heartbeat'})); $hb = 1800 unless (defined($hb)); my($min) = $d->{'rrd-min'}; $min = $target->{'rrd-min'} if (defined($target->{'rrd-min'})); $min = 'U' unless (defined($min)); my($max) = $d->{'rrd-max'}; $max = $target->{'rrd-max'} if (defined($target->{'rrd-max'})); $max = 'U' unless (defined($max)); my($cdef) = $d -> {'rrd-cdef'}; if ($dst eq 'COMPUTE' && !defined($cdef)) { Warn("Datasource $ds is of type $dst but tag " . "rrd-cdef is missing."); return; } my $rrd_dsname; if ($Common::global::gLongDSName) { $rrd_dsname = Common::Util::mungeDsName($dsname); } else { $rrd_dsname = "ds$dsCnt"; } if ($dst ne 'COMPUTE') { push @dsDesc, join(":", 'DS', $rrd_dsname, $dst, $hb, $min, $max); } else { push @dsDesc, join(":", 'DS', $rrd_dsname, $dst, $cdef); } $dsCnt++; } else { Warn("Datasource $ds referenced by target " . "type $ttype does not exist."); return; } } # now do RRA's my($rras) = $ttRef->{'rra'}; if (! $rras) { Warn("No rra tag found for target type $ttype."); return; } my($rra); foreach $rra (split(/\s*,\s*/, $rras)) { my($rraname) = lc($rra); my($r) = $main::gCT->configHash($name, 'rra', undef, $target); if (defined($r)) { if (defined $r->{$rraname}) { push(@rraDesc, "RRA:$r->{$rraname}"); } else { Warn("RRA $rra referenced by target " . "type $ttype does not exist."); return; } } else { # this shouldn't happen... right? Warn("Could not find RRA dictionary."); return; } } push(@arg, @dsDesc, @rraDesc); Info("Creating datafile $datafile for target name $tname."); Debug(join(' ', 'RRDs::create', @arg)); RRDs::create(@arg); if (my $error = RRDs::error()) { Warn("Cannot create $datafile: $error\n"); } return 1; } # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/collector_th0100755000114300000000000004575307770452734014553 0ustar bertdwheel#!/usr/local/bin/perl # -*- perl -*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. BEGIN { my $programdir = (($0 =~ m:^(.*/):)[0] || "./") . "."; eval "require '$programdir/cricket-conf.pl'"; #eval "require '/usr/local/etc/cricket-conf.pl'" # unless $Common::global::gInstallRoot; $Common::global::gInstallRoot ||= $programdir; } require 5.8.0; use lib "$Common::global::gInstallRoot/lib"; use Threaded; use RRDs 1.000101; use Common::Version; use Common::global; use Common::Options; use Common::Util; use Common::Map; use Common::HandleTarget; use ConfigTree::Cache; use Monitor; # Don't change these here! Set them in the Defaults file. These # should also not be removed because of backwards compatability my $wait_time = 1; my $wait_max = 240; my $max_threads = 100; my $is_threaded; # here's where the individual datasource routines live use snmp; use exec; use file; use field; # Remove the comment on the next line if you want to use sql logging # use sql; if (Common::Util::isWin32()) { eval "use wbem"; eval "use perfmon"; } Info("starting"); $start_time = time; # See the documentation on ds-source FUNC for why this defaults # to commented out. # use func; Common::Options::commonOptions(); Info("Starting collector: $Common::global::gVersion"); $gTargetCt = 0; $Common::global::isCollector = 1; $Common::global::gCT = new ConfigTree::Cache; $gCT = $Common::global::gCT; $gCT->Base($Common::global::gConfigRoot); $gCT->Warn(\&Warn); Info("calling init") if (defined($logging)); if (!$gCT->init()) { Die("Failed to open compiled config tree from " . "$Common::global::gConfigRoot/config.db: $!"); } my($recomp, $why) = $gCT->needsRecompile(); if ($recomp) { Warn("Config tree needs to be recompiled: $why"); system( (Common::Util::isWin32() ? 'perl ' : '') . "$Common::global::gInstallRoot/compile " . "-base $Common::global::gConfigRoot"); $gCT = new ConfigTree::Cache; $gCT->Base($Common::global::gConfigRoot); if (! $gCT->init()) { Die("Failed to open compiled config tree from " . "$Common::global::gConfigRoot/config.db: $!"); } } # if they gave us no subtrees to focus on, use the root of the config tree if ($#ARGV+1 == 0) { push @ARGV, '/'; } # foreach subtree to do # find the base node of that subtree # foreach leaf node of this subtree # process it my($subtree,%targets); foreach $subtree (@ARGV) { if ($gCT->nodeExists($subtree)) { $gCT->visitLeafs($subtree, \&handleTarget, \&handleTargetInstance, \&localHandleTargetInstance); } else { Warn("Unknown subtree $subtree."); } } foreach $subtree (@ARGV) { if ($gCT->nodeExists($subtree)) { $gCT->visitLeafs($subtree, \&handleTarget, \&checkTargetInstance); } else { Warn("Unknown subtree $subtree."); } } # Placeholder for threaded collection $th = 1; #foreach my $name (sort rand_sort keys %targets) { foreach my $name (sort keys %targets) { my(@mixedResults); my $type = $targets{$name}{'type'}; my $target = $targets{$name}{'target'}; my $dsList = $targets{$name}{'dsList'}; Debug("threaded: $target->{'threaded'}"); if(defined $target->{'threaded'} == 1) { $is_threaded = 1; Debug("Threading defined; using threaded collection"); $wait_time = $target->{'threaded_waittime'} if(defined($target->{'threaded_waittime'})); $wait_max = $target->{'threaded_waitmax'} if(defined($target->{'threaded_waitmax'})); $max_threads = $target->{'threaded_maxthreads'} if(defined($target->{'maxthreads'})); Debug("Thread wait_time: $wait_time; wait_max: $wait_max; max_threads = $max_threads"); ThreadedCollect::wait_for_thread($wait_time,$max_threads,$th); Info("Kick off thread"); ThreadedCollect::create_threads($name, $type, $target, $dsList, $th); $th++; } else { $is_threaded = 0; Debug("Threading not defined; using un-threaded collection"); localUpdateRRDDataFile($name,$type,$target,$dsList); } } # Make sure the threads clean themselves up if(defined($th)) { my $diff = time-$start_time; ThreadedCollect::wait_for_complete($wait_max,$max_threads,$wait_time); $diff = time-$start_time; } # print some summary stuff (number of targets, time taken) # before exiting. my($time) = runTime(); Info("Processed $gTargetCt targets in $time."); # only use strict for the subroutines use strict; sub rand_sort { rand() <=> rand (); } sub localUpdateRRDDataFile { my($name,$type,$target,$dsList) = @_; my($tname) = $target->{'auto-target-name'}; my($tpath) = $target->{'auto-target-path'}; my($datafile) = $target->{'rrd-datafile'}; my $agent_restart = 0; my(@data) = retrieveData($name,$type,$target,$dsList,\$agent_restart); if ($#data+1 == 0) { Warn("No data retrieved. Skipping RRD update."); return; } if (!defined($datafile)) { Warn("Could not find a datafile for $tname."); return; } if (! -f $datafile) { return unless newRRD($name, $target); } # look for date strings, suck em out. my($data, $when, @data2); foreach $data (@data) { if ($data =~ /@(\d+)/) { my($when2) = $1; if (defined($when) && ($when ne $when2)) { Warn("Found inconsistent times in retrieved data. " . "Using first one seen."); } else { $when = $when2; } $data =~ s/\@${when2}//; } push @data2, $data; } if (! defined($when)) { # if they didn't tell us when, use RRD's "now" syntax. $when = "N"; } # If an SNMP agent restart occurred, insert a dummy record consisting # of all "U" one second before the current sample. This causes RRD # to set the previous value of counters to undefined, avoiding weird # results when a counter goes negative because of a restart. if ($agent_restart && $when eq "N") { my @dummyresults = @data2; grep { $_ = 'U'} @dummyresults; Info("Inserting dummy record because of agent restart"); my $now = time(); RRDs::update($datafile, join(":", $now - 1, @dummyresults)); } RRDs::update($datafile, join(":", $when, @data2)); if (my $error = RRDs::error()) { Warn("Cannot update $datafile: $error\n"); } # if we were asked to copy this to someplace, go for it. if (defined($target->{'copy-to'})) { my($copyto) = $target->{'copy-to'}; my($to, $args) = split(/:/, $copyto, 2); $to = lc($to); if ($to eq "trap") { # In this case, args is expected to have SNMP info in # it. Example: trap:public@nms-101 # We just pass it straight thru to snmptrap. # This number lets our NMS identify this as a Cricket # data trap. The enterprise for the trap # is hardcoded in snmpUtils::trap. my($specific) = 3; snmpUtils::trap($args, $specific, "/$tpath/$tname", @data2); } elsif ($to eq "sql") { sqlUtils::sendto($args, @data2); } else { Warn("Unknown copy-to type: $to. Ignoring."); } } } sub localHandleTargetInstance { my($name, $target) = @_; my($tname) = $target->{'auto-target-name'}; my($tpath) = $target->{'auto-target-path'}; # first, dump the dict, to help debug things my($k, $v, $t); $t = "target $tname\n"; foreach $k (sort (keys(%{$target}))) { next if ($k eq "auto-target-name"); $v = $target->{$k}; $v = "[undef]" if (! defined($v)); $t .= "\t$k = $v\n"; } Debug($t); # skip this if it's a meta-target if ((defined($target->{'targets'})) || (defined($target->{'mtargets'}))) { Debug("Skipping meta target $tname"); return; } if (defined($target->{'collect'}) && isFalse($target->{'collect'})) { Debug("Skipping target $tname due to collect=false tag."); return; } $main::gTargetCt++; my $agent_restart = 0; my(@data) = retrieveParams($name, $target, \$agent_restart); } sub handleMultiTarget { # the collector completely ignores this -- it's only # used by the grapher. } sub retrieveData { my($name,$type,$target,$dsList,$restart_ref) = @_; my(@mixedResults); my $dsCount = $target->{'dsCount'}; my $tname = $target->{'auto-target-name'}; my $start = time; my %dataLog; my $dataLog; push(@mixedResults, map { $_ = $type . ":" . $_ } &{$main::gDSFetch{$type}}($dsList, $name, $target)); # Reassemble the data in the right order. my($line, @results); foreach $line (@mixedResults) { my($type, $index, $value) = split(/:/, $line, 3); # only take the first token from the line... that # way, they can put in-line comments in the returned data. # do this only for 'exec' and 'file' data sources if (lc($type) ne 'snmp') { $value =~ s/^\s*//; ($value) = split(/\s+/, $value, 2); } $results[$index] = $value; $dataLog{$index} = $value; } my $num_data = @results; my $diff = time - $start; # Make sure there are no gaps in the return data! # If any data is missing, make it undefined ("U") my($ctr, $missingData) = (0, 0); for $ctr (0 .. $#results) { if (! defined $results[$ctr]) { $results[$ctr] = "U"; $missingData = 1; } else { if ($results[$ctr] eq 'U') { $missingData = 1; } } } # Check the agent uptime with the poll interval. If the uptime is # less then one poll interval, notify our caller of the restart. if (defined($target->{'snmp-uptime'})) { my($agent_uptime) = pop @results; my($poll) = $target->{'rrd-poll-interval'}; $poll = 300 unless (defined($poll)); if ($agent_uptime ne "U" && $agent_uptime < $poll * 100) { Info("Agent uptime is less than poll interval"); $$restart_ref = 1 if (defined($restart_ref)); } } # if we are verifying, check the # fetched mapping key to make certain it's right my($mapkey, $mapRef, $match, $snmp, $baseOID, $oid); if (defined($target->{'--verify-mapkey--'})) { $mapkey = $target->{'--verify-mapkey--'}; $mapRef = $main::gCT->configHash($name, 'map', $mapkey, $target); if (defined($mapRef)) { $match = $mapRef->{'match'}; if (defined($match)) { $match = ConfigTree::Cache::expandString($match, $target, \&Warn); } $baseOID = $mapRef->{'base-oid'}; my($oidMap) = $main::gCT->configHash($name, 'oid', undef, $target); $oid = mapOid($oidMap, $baseOID); } else { Warn("Unknown mapkey. This should not happen."); } $snmp = $target->{'snmp'}; if (!defined($match) || !defined($snmp) || !defined($oid)) { Warn("Data needed to verify $mapkey is missing. " . "Skipping verification."); delete($target->{'--verify-mapkey--'}); } else { my($inst) = $target->{'inst'}; } my($fetchedKey) = pop @results; my($wrongInst); if ($match =~ /^\s*\/(.*)\/\s*$/) { $match = $1; $wrongInst = ($fetchedKey !~ /$match/i); } else { $wrongInst = ($fetchedKey ne $match); } if ($wrongInst) { # damn, they didn't match. this means we need to # fix the instance number using mapInstance, and # retry. Common::Map::mapInstance($name, $target); if (defined($target->{'inst'})) { # now that we have the correct inst, fetch again # (this time there is no need to verify) delete($target->{'--verify-mapkey--'}); @results = retrieveData($name, $target, undef); } else { # fill in all unknown, since the mapping key seems # to no longer exist #@results = (); #for ($ctr = 0; $ctr < $dsCount; $ctr++) { # push @results, "U"; #} } } } my($numRes) = $#results+1; if($numRes != $dsCount) { Warn("$dsCount datasources required, $numRes results returned!"); @results = (); } # Beautify the log output foreach my $key (sort {$a <=> $b} keys %dataLog) { my $val = $dataLog{$key}; $val =~ s/^U$/*** U ***/g; $dataLog .= "ds$key: $val, "; } # I'm too lazy to use substr(). Sue me. chop($dataLog); chop($dataLog); Info("Retrieved data for $tname: $dataLog"); Info("Some data is missing for $tname.") if ($missingData); return @results; } sub retrieveParams { my($name, $target, $restart_ref) = @_; my($tname) = $target->{'auto-target-name'}; my($inst) = $target->{'inst'}; if (defined($inst)) { $tname .= " ($inst)"; } my($ttype) = lc($target->{'target-type'}); my($ttRef) = $main::gCT->configHash($name, 'targettype', $ttype, $target); if (! $ttRef) { Warn("Unknown target type: $ttype."); return; } Debug("Retrieving data for target $tname ($ttype)"); # Determine the list of data sources for this target. my(@targetDSs) = split(/\s*,\s*/, $ttRef->{'ds'}); if (!@targetDSs) { Warn("Could not find any datasources for target type $ttype."); return (); } # take the count before we start dicking with it # below... my($dsCount) = $#targetDSs + 1; # if we need to fetch a key to verify a cached instance, # we append it to targetDSs here. my($mapkey, $mapRef, $match, $snmp, $baseOID, $oid); if ($target->{'--verify-mapkey--'}) { $mapkey = $target->{'--verify-mapkey--'}; $mapRef = $main::gCT->configHash($name, 'map', $mapkey, $target); if (defined($mapRef)) { $match = $mapRef->{'match'}; if (defined($match)) { $match = ConfigTree::Cache::expandString($match, $target, \&Warn); } $baseOID = $mapRef->{'base-oid'}; my($oidMap) = $main::gCT->configHash($name, 'oid', undef, $target); $oid = mapOid($oidMap, $baseOID); } else { Warn("Unknown mapkey. This should not happen."); } $snmp = $target->{'snmp'}; if (!defined($match) || !defined($snmp) || !defined($oid)) { Warn("Data needed to verify $mapkey is missing. " . "Skipping verification."); delete($target->{'--verify-mapkey--'}); } else { my($inst) = $target->{'inst'}; my($newds) = "--snmp://${snmp}/${oid}.${inst}"; push @targetDSs, $newds; } } if (defined($target->{'snmp-uptime'})) { my $ds = "--snmp://$target->{'snmp'}/$target->{'snmp-uptime'}"; push @targetDSs, $ds; } # this will hold a hash of ds-method names. the values will # be a ref to a list of the sources that get passed to # the ds-method later. my(%targetDataSources) = (); my($dsIndex) = 0; my($ds); foreach $ds ( @targetDSs ) { my($dataSource); if ($ds =~ /^--/) { # this is a hacked one, from the verify code, above. $dataSource = $ds; $dataSource =~ s/^--//; } else { # this is a normal ds, so go look it up. my($dsRef) = $main::gCT->configHash($name, 'datasource', lc($ds), $target); if ($dsRef) { # computed data sources need to be excluded fetch if ($dsRef->{'rrd-ds-type'} eq 'COMPUTE') { $dsCount--; # don't penalize for missing these DS next; } $dataSource = $dsRef->{'ds-source'}; $dataSource = ConfigTree::Cache::expandString($dataSource, $target, \&Warn); } else { Warn("Datasource named $ds not found."); next; } } my($dsMethod,$dsLine) = split(':', $dataSource, 2); # create a hash entry which is an array of datasources # of the same TYPE (i.e. snmp, shell, etc). # NOTE: The datasource type is REPLACED by the datasource # index. This is so that we can be sure to reassemble the # data source return value array in the right order. push(@{ $targetDataSources{lc($dsMethod)} }, "$dsIndex:$dsLine"); Debug("ds$dsIndex is: $dsLine"); $dsIndex++; } # For each different data source type (snmp, exec, etc.) # call the fetcher to retrieve the data. my($dsList, $type, @mixedResults,$diff,$error); while (($type, $dsList) = each %targetDataSources) { if (defined ($main::gDSFetch{$type})) { my $starttime = time; $targets{$name}{'type'} = $type; $targets{$name}{'target'} = $target; $targets{$name}{'dsList'} = $dsList; $target->{'dsCount'} = $dsCount; Debug("Collecting params for $name"); } else { Warn("Could not find a fetcher with type $type to " . "fetch data for $tname."); } } } sub newRRD { # Create a new RRD file base on the contents of the # referenced dictionary. my($name, $target) = @_; my($datafile) = $target->{'rrd-datafile'}; my($tname) = $target->{'auto-target-name'}; my($ttype) = lc($target->{'target-type'}); # Create the data directory if it does not exist: $datafile =~ /(.*)\/.*$/; my($dataDir) = $1; if (! -d $dataDir) { Common::Util::MkDir($dataDir); } # Start rrd one week ago. my($week) = (60 * 60 * 24 * 7); my($start) = time - $week - 1; my($poll) = $target->{'rrd-poll-interval'}; $poll = 300 unless (defined($poll)); my(@arg) = ($datafile, "--start", $start, "--step", $poll); my($type, $val); my($valid) = 0; my(@dsDesc) = (); my(@rraDesc) = (); my($dsCnt) = 0; my($ttRef) = $main::gCT->configHash($name, 'targettype', $ttype, $target); if (! $ttRef) { Warn("Unknown target type: $ttype."); return; } # first, process the ds tag my($dses) = $ttRef->{'ds'}; if (! $dses) { Warn("No ds tag found for target type $ttype."); return; } my($ds); foreach $ds (split(/\s*,\s*/, $dses)) { my($dsname) = lc($ds); my($d) = $main::gCT->configHash($name, 'datasource', $dsname, $target); if (defined($d)) { my($dst) = $d->{'rrd-ds-type'}; $dst = "GAUGE" unless (defined($dst)); $dst = uc($dst); my($hb) = $d->{'rrd-heartbeat'}; $hb = $target->{'rrd-heartbeat'} if (defined($target->{'rrd-heartbeat'})); $hb = 1800 unless (defined($hb)); my($min) = $d->{'rrd-min'}; $min = $target->{'rrd-min'} if (defined($target->{'rrd-min'})); $min = 'U' unless (defined($min)); my($max) = $d->{'rrd-max'}; $max = $target->{'rrd-max'} if (defined($target->{'rrd-max'})); $max = 'U' unless (defined($max)); my($cdef) = $d -> {'rrd-cdef'}; if ($dst eq 'COMPUTE' && !defined($cdef)) { Warn("Datasource $ds is of type $dst but tag " . "rrd-cdef is missing."); return; } if ($dst ne 'COMPUTE') { push @dsDesc, join(":", 'DS', "ds$dsCnt", $dst, $hb, $min, $max); } else { push @dsDesc, join(":", 'DS', "ds$dsCnt", $dst, $cdef); } $dsCnt++; } else { Warn("Datasource $ds referenced by target " . "type $ttype does not exist."); return; } } # now do RRAs my($rras) = $ttRef->{'rra'}; if (! $rras) { Warn("No rra tag found for target type $ttype."); return; } my($rra); foreach $rra (split(/\s*,\s*/, $rras)) { my($rraname) = lc($rra); my($r) = $main::gCT->configHash($name, 'rra', undef, $target); if (defined($r)) { if (defined $r->{$rraname}) { push(@rraDesc, "RRA:$r->{$rraname}"); } else { Warn("RRA $rra referenced by target " . "type $ttype does not exist."); return; } } else { # this shouldn't happen... right? Warn("Could not find RRA dictionary."); return; } } push(@arg, @dsDesc, @rraDesc); Info("Creating datafile $datafile for target name $tname."); Debug(join(' ', 'RRDs::create', @arg)); RRDs::create(@arg); if (my $error = RRDs::error()) { Warn("Cannot create $datafile: $error\n"); } return 1; } # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 cricket-1.0.5/compile0100755000114300000000000000476507447507307013516 0ustar bertdwheel#!/usr/local/bin/perl -w # -*- perl -*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. BEGIN { my $programdir = (($0 =~ m:^(.*/):)[0] || "./") . "."; eval "require '$programdir/cricket-conf.pl'"; eval "require '/usr/local/etc/cricket-conf.pl'" unless $Common::global::gInstallRoot; $Common::global::gInstallRoot ||= $programdir; } use lib "$Common::global::gInstallRoot/lib"; use Common::Version; use Common::global; use Common::Log; use Common::Options; use Common::Util; use Common::Map; use Common::HandleTarget; use ConfigTree::Node; my $starttime = time(); Common::Options::commonOptions(); Info("Starting compile: $Common::global::gVersion"); my($nodeCt, $fileCt) = (0, 0); $gCT = new ConfigTree::Node; $gCT->info(\&Common::Log::Info); $gCT->warn(\&Common::Log::Warn); $gCT->debug(\&Common::Log::Debug); $gCT->Base($Common::global::gConfigRoot); if (&check_and_set_lock) { exit; } $gCT->init(); $gCT->processTree(); ($nodeCt, $fileCt) = $gCT->compile(); # log summary info before exiting $time = runTime($starttime); Info("Processed $nodeCt nodes (in $fileCt files) in $time."); &unlock; exit; sub check_and_set_lock { my $lockfile = $gCT->Base() . "/config.db.lock"; if (-f $lockfile) { my($db_mtime) = (stat($lockfile))[9]; # Do not recompile if another process already is busy compiling if ((time() - $db_mtime) < 900) { Warn("Skipping recompile; already in progress"); return 1; } else { # But warn if that other process is dead Warn("Forcing recompile; previous compile aborted?"); } } open(LOCK, ">$lockfile") || die "Can't create $lockfile"; print LOCK "$$\n"; close LOCK; return 0; } sub unlock { my $lockfile = $gCT->Base() . "/config.db.lock"; unlink $lockfile; } cricket-1.0.5/configure0100755000114300000000000000041207047764266014035 0ustar bertdwheel#!/bin/sh # This just runs util/relocate-perl, since people are in a habit # of looking for a configure script in the root. Also, someday # this could become a full-fledged autoconf generated configure # script, if it turns out we need it. exec util/relocate-perl cricket-1.0.5/cricket-conf.pl.sample0100644000114300000000000000607310012216664016277 0ustar bertdwheel# Sample Cricket configuration file. # This file is looked for in the same directory where the executable # scripts are located, or in /usr/local/etc/cricket-conf.pl. # In its absence, the rules for Cricket 1.0.2 and previous versions # apply, i.e. Cricket looks at your HOME directory to find its # components. # # The commented out variables are optional and should probably # not be tweaked unless you know what you're doing. # # It is possible to modify the Perl environment, e.g. by adding # "use lib /foo" to this file. # $Id: cricket-conf.pl.sample,v 1.12 2004/02/10 18:06:44 xkilian Exp $ package Common::global; $gCricketHome = "/home/cricket"; $gInstallRoot = "$gCricketHome/cricket"; #$gConfigRoot = "$gCricketHome/cricket-config"; # Whether to optimize config tree access for the collector by # copying the database into memory on startup. # Switch off if your collector becomes too big in run time. # Most sites will probably want slurp, this is the default. #$gDbAccess = "slurp"; # Slurp the database into memory #$gDbAccess = "lookup"; # Use normal DB lookups # Where cricket caches generated images. #$gCacheDir = "/tmp/cricket-cache"; # It is strongly recommended that you *not* set a global logLevel. # Use the CRICKET_LOG_LEVEL environment variable or the # -logLevel option instead. #$gLogLevel = "info"; # It is strongly recommended that you *not* set a global logFormat. # Use the CRICKET_LOG_FORMAT environment variable or the # -logFormat option instead. # Three formats are supported by Log.pm. Minimal, standard and extended. # Consult the documentation. Default format is standard. #$gLogFormat = "standard"; # Pick the style of URL you want Cricket to use when generating # self-referencing URLs (either URL's to targets, or URL's to # graphed images). # It is recommended not to touch this unless you need to. $gUrlStyle="classic"; # What Cricket before 1.0.4 did. #$gUrlStyle="relative"; # Required if Cricket sits behind a reverse proxy #$gUrlStyle="pathinfo"; # Encode the target in the URL path for authentication # Set this to "1" to enable the new (and experimental!) feature to # use long RRD datasource names. See the CHANGES file before enabling # this. $gLongDSName=0; # Set to "1" to make collector log fully qualified datasource names. # e.g. When enabled, you get "/Switches/Cat4000/Core/FastEthernet0_1" # instead of just "FastEthernet0_1". $gLogFullPath=0; # Set this to "1" to enable the tag search feature. When enabled # this adds a small search box to the bottom of the grapher # window, which can be used to do a recursive search on # values contained in the chassis target. It is currently # limited to searching for a value in the snmp-host and display-name tags. $gEnableSearch=1; # For users of monitoring thresholds. This variable defines how to treat # conditions where a threshold is tested against an unavailable value or # a database lookup error. # Set to 1, and it will treat unvailable values (or NaNs) as alarms. # Set to 0 or undefined it will not treat NaNs as alarms. $gEnableNoValueAlarms = 0; cricket-1.0.5/grapher.cgi0100755000114300000000000023655510010740166014241 0ustar bertdwheel#!/usr/local/bin/perl -w # -*- perl -*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. BEGIN { # If you need to change anything in this script, it should only # be here, in this BEGIN block. See the README for more info. my $programdir = (($0 =~ m:^(.*/):)[0] || "./") . "."; eval "require '$programdir/cricket-conf.pl'"; if (!$Common::global::gInstallRoot && -l $0) { eval { my $link = readlink($0); my $dir = (($link =~ m:^(.*/):)[0] || "./") . "."; require "$dir/cricket-conf.pl"; } } eval "require '/usr/local/etc/cricket-conf.pl'" unless $Common::global::gInstallRoot; $Common::global::gInstallRoot ||= $programdir; $Common::global::gConfigRoot ||= 'cricket-config'; $Common::global::isGrapher = 1; } use lib "$Common::global::gInstallRoot/lib"; use CGI qw(fatalsToBrowser); use RRDs 1.000101; use Digest::MD5; use RPN; use RRD::File; use ConfigTree::Cache; use Common::Version; use Common::global; use Common::Log; use Common::Options; use Common::Util; use Common::Map; use Common::HandleTarget; # Set a safe path. Necessary for set[ug]id operation. $ENV{PATH} = "/bin:/usr/bin"; my $gLongDSName = $Common::global::gLongDSName; # default to warn. You might want to change this to 'debug' when # working on Cricket configs, or hacking Cricket itself. $log = 'warn'; Common::Log::setLevel($log); # cache cleaning params $gPollingInterval = 5 * 60; # defaults to 5 minutes # Determine which URL style to use. Classic uses self_url, which creates # new URL's by parrotting its own URL with modifications. Relative only # uses the bits after the last slash in the URL, which makes URL's # shorter and eases proxying. Both pass the target name in a URL parameter. # Pathinfo passes the target as path components after the CGI name, # which makes life easier for folks using Apache "Location" access # restrictions. And they all have their drawbacks too. Pick one. $Common::global::gUrlStyle ||= "classic"; my $gUseSelfUrl = 0; my $gUseRelativeUrl = 0; my $gUsePathInfo = 0; if ($Common::global::gUrlStyle eq "pathinfo") { $gUsePathInfo = 1; } elsif ($Common::global::gUrlStyle eq "relative") { $gUseRelativeUrl = 1; } else { $gUseSelfUrl = 1; } $gQ = new CGI; fixHome($gQ); initConst(); $gColorInit = 0; $Common::global::gCT = new ConfigTree::Cache; $gCT = $Common::global::gCT; $gCT->Base($Common::global::gConfigRoot); $gCT->Warn(\&Warn); if (! $gCT->init()) { Die("Failed to open compiled config tree from " . "$Common::global::gConfigRoot/config.db: $!"); } $gError = ''; my($recomp, $why) = $gCT->needsRecompile(); if ($recomp) { $gError .= "Config tree needs to be recompiled: $why"; } my($type) = $gQ->param("type"); $type = "html" if (! defined($type)); if ($type ne 'html') { doGraph(); } else { doHTMLPage(); } sub doHTMLPage { my($ct) = $gCT; my($name) = urlTarget($gQ); $name = "/" if (! $name); my($targRef) = $ct->configHash($name, 'target'); ConfigTree::Cache::addAutoVariables($name, $targRef, $Common::global::gConfigRoot); my($tname) = $targRef->{'auto-target-name'}; if ($ct->isLeaf($name)) { # display a target page # if this is a scalar-instance target, then this is where # we put the data # # if this is a vector-instance target, then we put an # instance selector here # inst selection: # if it's in the params, use that first # (i.e. they have already been through inst selection) # if it's in the target def, use that (but do # preliminary mapping on it if necessary) # otherwise, default to no instance my($inst); my($chosenInst) = 0; my($needEval) = 0; if (defined($gQ->param('inst'))) { # chosenInst controls showing the inst in the title $chosenInst = 1; $inst = $gQ->param('inst'); $targRef->{'inst'} = $inst; $needEval = 0; } elsif (defined($targRef->{'inst'})) { # this instance came from the config file, so # it's probably a router-interface, and we don't # need to be told the instance numbers for these $chosenInst = 0; # we will map this puppy later, if necessary, so # get things ready before the eval(). Common::Map::mapPrepareInstance($targRef); $inst = $targRef->{'inst'}; $inst = ConfigTree::Cache::expandString($inst, $targRef, \&Warn); $needEval = 1; } else { $needEval = 0; } my(@inst) = (); if ($needEval) { @inst = Eval($inst); } if ($#inst+1 > 1) { # make the instance selection widget... htmlHeader($name, $targRef, "Instance selection for $tname"); print htmlCurrentPath($ct, $targRef, $name); print "There are multiple instances for this target. Please"; print " choose one:

\n"; print "

"; my($ins) = $targRef->{'inst-names'}; $ins = ConfigTree::Cache::expandString($ins, $targRef) if defined($ins); my($instNameMap) = makeInstMap($ins, $inst); my($ct) = 0; foreach $inst (@inst) { # make the URL and the link text -- pass the name # through, since it's hard to find out later. my($text) = $inst; if ($instNameMap->{$inst}) { $text = $instNameMap->{$inst}; $gQ->param('instname', $text); } else { $gQ->delete('instname'); } $gQ->param('inst', $inst); my($me) = makeUrl($gQ); my($link) = "$text "; print "" if (($ct % 5) == 0); print "
$link"; $ct++; } print "

\n"; } else { # this is a scalar instance -- display a set of images # (plus a bunch of stuff to handle multi-targets) # put the view into the target dict, so it's # there if they want to use it. my($view) = lc $gQ->param('view'); if (defined($view)) { $targRef->{'auto-view'} = $view; } # if they gave us an array of one instance, put that # into the inst tag, and make certain it gets passed # forward to doGraph by setting $inst if ($inst[0]) { $targRef->{'inst'} = $inst[0]; $inst = $inst[0]; } ConfigTree::Cache::expandHash($targRef, $targRef, \&Warn); Common::Map::mapInstance($name, $targRef); # check to make certain that the key and the target # are set up right. my($md5) = new Digest::MD5; $md5->add($targRef->{'auto-target-name'}); my($hash) = $md5->hexdigest(); if ($hash eq '808ff9abef8942fcb2ac676abe4ecc5e') { Warn("Key is out of date."); print eval(unpack("u*", $gKey)); return; } # use display-name if set my($title); if (defined($targRef->{'display-name'})) { $title = "Graphs for $targRef->{'display-name'}"; } else { $title = "Graphs for $tname"; } if ($chosenInst) { my($in) = $gQ->param('instname'); if ($in) { $title .= " ($in)"; } else { $title .= " (instance: $inst)"; } } # find the ds names based on the view name my($ttype) = lc($targRef->{'target-type'}); my($ttRef) = $ct->configHash($name, 'targettype', $ttype, $targRef); # now, we gather up a dslist: either it comes from # the view, or it's simply all the ds's in the target type. my ($enableHoltWinters, $viewRef) = (0,undef); my($dslist) = (undef); if (defined($view)) { my($v); foreach $v (split(/\s*,\s*/, $ttRef->{'view'})) { # views are like this: "cpu: cpu1load cpu5load" my($vname, $dss) = split(/\s*:\s*/, $v, 2); if ($view eq lc $vname) { # check here for a view definition $viewRef = $ct->configHash($name, 'view', $view, $targRef); if (defined($viewRef) && exists($viewRef->{'elements'})) { Debug("Found view defintion for $view"); $dslist = $viewRef->{'elements'}; $enableHoltWinters = 1 if ( exists $viewRef->{'holtwinters'} && isTrue($viewRef->{'holtwinters'})); } else { # ignore all view definitions for backwards # compatibility $viewRef = undef; # parse view name for HoltWinters special tag $enableHoltWinters = 1 if ($view =~ /[hH]olt[wW]inters/); $dslist = $dss; } $dslist =~ s/\s*$//; # remove trailing space # make it comma-separated unless it already is $dslist =~ s/\s+/,/g unless (index($dslist,",") != -1); } } if (! $dslist) { Error("Failed to get dslist from view name."); } } else { $dslist = $ttRef->{'ds'}; # squeeze out any extra spaces $dslist = join(',', split(/\s*,\s*/, $dslist)); } # handle multi-targets... if we have a targets attribute, # get ready to loop on each of it's items my(@targets, $isMulti, $plural); if (defined($targRef->{'targets'})) { my $targets = $targRef->{'targets'}; my($path) = $targRef->{'auto-target-path'}; my($target); foreach $target (split(/\s*;\s*/, $targets)) { # this allows local targets to use shorter names $target = "$path/$target" unless ($target =~ /^\//); # This regex lowercases just the last item: $target =~ s:([^/]+)$:lc $1:e; # now, look for things like '/target:(0..4)' # and add all of them correctly. my($t, $i) = split(/\s*:\s*/, $target); if (defined($i)) { my(@j); Debug("Will be evaling $i"); @j = Eval($i); my($ct); foreach $ct (@j) { push @targets, "$t:$ct"; } } else { push @targets, $target; } } $isMulti = 1; $plural = "s"; } else { @targets = ( $name ); $isMulti = 0; $plural = ""; } # determine the reqested temporal ranges my($reqRanges) = $gQ->param('ranges'); if (!defined($reqRanges)) { $reqRanges = SetDefaultRange($targRef,$viewRef); Debug("Default ranges utilized: $reqRanges"); } my(%dsDescr) = (); # if this tag is present, # the user wants to display a Holt-Winters graph my($hwParam) = $gQ->param('hw'); # Holt-Winters links my(@hwlinks); @hwlinks = makeHwNavLinks() if ($enableHoltWinters); my(@links) = makeNavLinks($reqRanges); htmlHeader($name, $targRef, $title); if(!$targRef->{'summary-loc'} || $targRef->{'summary-loc'} eq "top") { print htmlCurrentPath($ct, $targRef, $name); print "\n"; print "\n"; print "
\n"; if (! $isMulti) { %dsDescr = doHTMLSummary($name, $tname, $targRef, $ttRef, $dslist, $viewRef); } else { if ($targRef->{'long-desc'}) { print "$targRef->{'long-desc'}

\n"; } else { print " "; } } print "

\n"; print "Time Ranges:

\n", join("
\n", @links); # add a tag for Holt-Winters if ($enableHoltWinters) { print "

Aberrant Behavior Detection:

\n", join("
\n", @hwlinks); } print "

\n"; } if (defined($targRef->{'target-html'})) { print $targRef->{'target-html'}; } my($range, @ranges); @ranges = getRanges($reqRanges); foreach $range (@ranges) { my ($label); # add perimeter stuff for Holt-Winters as appropriate if (defined($hwParam)) { if ($hwParam eq "confidence") { $label = "Confidence Bounds: Hourly"; } elsif ($hwParam eq "failures") { $label = "Failures (exceeds confidence bounds): Hourly"; } else { $label = "Failures (exceeds confidence bounds): Hourly"; } } else { $label = rangeToLabel($range); } print "

$label graph${plural}

\n"; # If we have this, we should use it my(@targetsSD); my($targetsSD) = $targRef->{'targets-short-desc'}; if (defined($targetsSD)) { @targetsSD = Eval($targetsSD); } # And if we have this, we should use this. # targets-long-desc overrides targets-short-desc my(@targetsLD); my($targetsLD) = $targRef->{'targets-long-desc'}; if (defined($targetsLD)) { @targetsLD = Eval($targetsLD); } my($i)=0; my($thisTarget, $thisInst, $thisTarget2); foreach $thisTarget (@targets) { my($linkurl); if ($isMulti) { # Load the config for each of these so I can pull # out the short-desc field. Use local variables # so nothing else breaks # if there is an inst defined, get it ($thisTarget2, $thisInst) = split(/\s*:\s*/, $thisTarget, 2); my($targRef) = $gCT->configHash($thisTarget2, 'target', undef, 1); my($instNameMap) = makeInstMap( $targRef->{'inst-names'}, $targRef->{'inst'}); my($origInst) = $targRef->{'inst'}; my(@origInst); if (defined($origInst)) { @origInst = Eval(quoteString($origInst)); } my($desc); if ((defined($targetsSD[$i])) && ($targetsSD[$i] ne '')) { $desc = $targetsSD[$i]; } else { $desc = $targRef->{'short-desc'}; } # Create the URL link that I'll use in case # somebody clicks on the title or the graph $gQ->delete_all(); $gQ->param('ranges', 'd:w'); urlTarget($gQ, $thisTarget2); $gQ->param('inst', $thisInst) if (defined($thisInst)); if (defined($view)) { $gQ->param('view', $view); } $linkurl = makeUrl($gQ); print ""; # construct the title of each target # use targets-long-desc if defined, else # construct a reasonable default my($name); if ((defined($targetsLD[$i])) && ($targetsLD[$i] ne '')) { $name = "

$targetsLD[$i]

"; } else { if (defined($thisInst)) { my($n) = $instNameMap->{$thisInst}; if ($n) { $name="

$thisTarget2 ($n)"; } else { $name="

$thisTarget2 " . "(instance $thisInst)"; } } else { $name="

$thisTarget"; } if (! defined($desc) || $desc eq '') { $name .= "

"; } else { $name .= " ($desc)"; } } print "\n"; print "$name"; print "
\n"; } else { # this is not a multi-target, so just # use the current setting for inst (even # if it is undef) $thisInst = $inst; $thisTarget2 = $thisTarget; } my($gRef) = $ct->configHash($name, 'graph', '--default--', $targRef); # use view parameters if defined if (defined($viewRef)) { mergeHash($gRef,$viewRef,1); } my($widthHint) = graphParam($gRef, 'width-hint', undef); $widthHint = "width=$widthHint" if ($widthHint); $widthHint = "" unless ($widthHint); my($heightHint) = graphParam($gRef, 'height-hint', undef); $heightHint = "height=$heightHint" if ($heightHint); $heightHint = "" unless ($heightHint); my($defFmt) = 'png'; my($bv) = ($ENV{'HTTP_USER_AGENT'} =~ /\/(\d)/); if (defined($bv) && $bv <= 3) { $defFmt = 'gif'; } my($format) = graphParam($gRef, 'graph-format', $defFmt); my($cache) = $gQ->param('cache'); # create the mini-graph URL $gQ->delete_all(); $gQ->param('type', $format); urlTarget($gQ, $thisTarget2); $gQ->param('inst', $thisInst) if defined($thisInst); if (defined($viewRef)) { $gQ->param('view', $view); } else { $gQ->param('dslist', $dslist); } $gQ->param('range', $range); $gQ->param('hw',$hwParam) if (defined($hwParam)); # this parameter is to trick Netscape into # always asking the CGI for the image, instead # of trying to cache it inappropriately $gQ->param('rand', int(rand(1000))); # pass thru the value of the cache param, if given $gQ->param('cache', $cache) if (defined($cache)); my($me) = makeUrl($gQ); if (! $ENV{'MOD_PERL'}) { $me =~ s/grapher\.cgi/mini-graph\.cgi/; } if ($isMulti) { print ""; print ""; print "\n"; } else { print "\n"; } print "

"; $i++; } } # display the datasource descriptions my(@dss); @dss = sort { $dsDescr{$a}->[0] <=> $dsDescr{$b}->[0] } keys(%dsDescr); if ($#dss+1 > 0) { print "

About the data...

\n"; print "
\n"; my($ds); foreach $ds (@dss) { my($order, $legend, $desc) = @{$dsDescr{$ds}}; print "\n"; print "
$legend
\n"; print "
$desc
\n"; print "

\n"; } print "

\n"; } if ($targRef->{'summary-loc'} && $targRef->{'summary-loc'} eq "bottom") { print htmlCurrentPath($ct, $targRef, $name); print "\n"; print "\n"; print "
\n"; if (! $isMulti) { %dsDescr = doHTMLSummary($name, $tname, $targRef, $ttRef, $dslist, $viewRef); } else { if ($targRef->{'long-desc'}) { print "$targRef->{'long-desc'}

\n"; } else { print " "; } } print "

\n"; print "Time Ranges:

\n", join("
\n", @links); if ($enableHoltWinters) { print "

Aberrant Behavior Detection:

\n", join("
\n", @hwlinks); } print "

\n"; } } } else { # there was no explicit target name, so we need to give them a # target and directory list my $title; if (defined($targRef->{'display-name'})) { $title = "Choose a target for $targRef->{'display-name'}"; } else { $title = "Choose a target :"; } htmlHeader($name, $targRef, $title); my(@children) = $ct->getChildren($name); # check for search parameter to display only matching targets my($searchCrit) = $gQ->param("search"); $gQ->delete("search") if (defined($searchCrit)); if ($searchCrit) { @children = (); my %children; foreach $subtree ($name) { if ($gCT->nodeExists($subtree)) { $gCT->visitLeafs($subtree, \&searchHandleTarget, $searchCrit, \%children); } else { Warn("Unknown subtree $subtree."); } } @children = keys %children if (scalar keys %children); } my($targs, $t, @targets, @dirs); foreach $t (@children) { my($tRef) = $ct->configHash($t, 'target', undef, 1); my($tn) = $tRef->{'auto-target-name'}; $targs->{$tn} = $tRef; $targs->{$tn}->{'--name--'} = $t; if ($ct->isLeaf($t)) { push @targets, $tn; } else { push @dirs, $tn; } } # Here, we sort the targets according to their 'order' # attributes. If there is no order attribute, we use # 0. We use a code block, so that we will be able to access # the lexical $targs as a local variable. @targets = sort { my($ordera, $orderb); $ordera = $targs->{$a}->{'order'}; $ordera = 0 unless defined($ordera); $orderb = $targs->{$b}->{'order'}; $orderb = 0 unless defined($orderb); # sort reverse of "order", then in forward alphanumeric order $orderb <=> $ordera || $a cmp $b; } @targets; print htmlCurrentPath($ct, $targRef, $name); if ($#targets+1 > 0) { my($doDesc) = 1; if ($targs->{$targets[0]}->{'disable-short-desc'}) { $doDesc = 0; } print "

Targets that are available:

\n"; print "\n"; if ($doDesc) { print ""; print " \n"; } else { print "\n"; } my($ttRef, $ttName); my($item); foreach $item (@targets) { # Skip display of targets that are "hidden".The data is # presumably being presnted in an "mtarget" elsewhere. if (defined $targs->{$item}->{'hide'} and isTrue($targs->{$item}->{'hide'})) { next; # Skip to next target. } my($desc); if (defined($targs->{$item}->{'short-desc'})) { $desc = $targs->{$item}->{'short-desc'}; } # don't want empty descriptions if (! defined($desc) || $desc eq '') { $desc = " "; } # first, reset the target parameter for the coming # links. my($newTarg) = "$name/$item"; urlTarget($gQ, $newTarg); my($itemName) = $item; if (defined($targs->{$item}->{'display-name'})) { $itemName = $targs->{$item}->{'display-name'}; } # Decide on the itemName if (defined($targs->{$item}->{'targets'})) { $itemName .= " (multiple targets)"; } elsif (defined($targs->{$item}->{'mtargets'})) { $itemName .= " (aggregated targets)"; } else { my($name) = $targs->{$item}->{'--name--'}; } # now, decide if there are multiple views for this target type # step one, get a good ttRef to use. my($ttype) = lc($targs->{$item}->{'target-type'}); if (!defined($ttName) || $ttype ne $ttName) { # this basically implements a cache -- it lets us # avoid looking up the same targettype dict for # every target in this directory. $ttRef = $ct->configHash("$name/$item", 'targettype', $ttype); $ttName = $ttype; } my($views) = $ttRef->{'view'}; # if it's set, views looks like this: # cpu: cpu1min cpu5min,temp: tempIn tempOut # or for defined top-level views: # cpu, temp if (defined($views)) { my($v, $links); $links = ""; foreach $v (split(/\s*,\s*/, $views)) { my($vname, $junk) = split(/\s*:\s*/, $v); my $viewRef = $ct->configHash($name, 'view', lc $vname); my $vdesc = $viewRef->{'label'}; $vdesc ||= $vname; # put it in just long enough to get a URL out $gQ->param('view', $vname); my($me) = makeUrl($gQ); $gQ->delete('view'); $links .= "[ $vdesc ]\n"; } print "\n"; } else { my($me) = makeUrl($gQ); my($link) = "$itemName"; print "\n"; } if ($doDesc) { print "\n"; } else { print "\n"; } } print "
NameDescription
Name
$itemName
" . "   \n$links
$link$desc

\n"; } if ($#dirs+1 > 0) { print "

Directories you can jump to:

\n"; print "\n"; print ""; print " \n"; my($item); foreach $item (sort @dirs) { my($desc) = " "; $desc = $targs->{$item}->{'directory-desc'} if ($targs->{$item}->{'directory-desc'}); my($itemPath) = $targs->{$item}->{'auto-target-path'}; my($newTarg) = "$itemPath/$item"; $newTarg =~ s#^\/\/#\/#; urlTarget($gQ, $newTarg); my($me) = makeUrl($gQ); my($link) = "$item"; print "\n"; } print "
NameDescription
$link$desc

\n"; } if ($Common::global::gEnableSearch) { urlTarget($gQ, $name); print "

\n"; print "Tag search: "; print "\n"; print "
\n"; } } htmlFooter($name, $targRef); return; } sub SetDefaultRange { my ($targetRef, $viewRef) = @_; my ($defRange); if (defined($targetRef->{'targets'})) { $defRange = 'd'; } elsif (defined($targetRef->{'mtargets'})) { $defRange = 'd:w'; } else { if (defined($viewRef) && exists($viewRef->{'default-ranges'})) { $defRange = $viewRef->{'default-ranges'}; } else { $defRange = 'd:w'; } } return $defRange; } sub doHTMLSummary { my($name, $tname, $targRef, $ttRef, $dslist, $viewRef) = @_; my($tpath); print "

Summary

\n"; print $targRef->{'long-desc'}, "

\n" if (defined($targRef->{'long-desc'})); my($yaxis, $i, $dsname, %dsmap); my(@mtargets) = (); my($isMTargets, $isMTargetsOps) = 0; # See if we are a multi-target if (defined($targRef->{'mtargets'})) { # this allows local targets to use shorter names my($path) = $targRef->{'auto-target-path'}; my($m); foreach $m (split(/\s*;\s*/, ($targRef->{'mtargets'}))) { $m = "$path/$m" unless ($m =~ /^\//); push @mtargets, $m; } $isMTargets = 1; } else { @mtargets = ( $name ); } # See if we are doing an operation or not my($MTargetsOps); if (defined($targRef->{'mtargets-ops'})) { $isMTargetsOps = 1; $MTargetsOps = $targRef->{'mtargets-ops'}; } # prepare a dsmap, using the targettype dict %dsmap = makeDSMap($ttRef->{'ds'}); my(%dsnamemap) = makeDSNameMap($ttRef->{'ds'}); my(%dsDescr, @units, @dsnum, @dsnames, $rrdfile, $rrd); my($order) = 0; my($printOnce)=0; my(%str); my($thisName); foreach $thisName (@mtargets) { if ($isMTargets) { # This regex lowercases just the last item in the thisName path: $thisName =~ s:([^/]+)$:lc $1:e; $targRef = $gCT->configHash($thisName, 'target', undef, 1); $tname = $targRef->{'auto-target-name'}; } else { # targRef and tname are already set right } $rrdfile = $targRef->{'rrd-datafile'}; $rrd = new RRD::File ( -file => $rrdfile ); if ($rrd) { $rrd->open(); } if ($rrd && $rrd->loadHeader()) { if (($isMTargets) && (!$isMTargetsOps)) { print "Values at last update for $tname:
"; } elsif (!$printOnce) { print "Values at last update:
"; $printOnce = 1; } print "\n"; $i = 1; my($dsname); foreach $dsname (split(/,/, $dslist)) { $dsname = lc($dsname); my($dsRef) = $gCT->configHash($thisName, 'datasource', $dsname, $targRef); my($gRef) = $gCT->configHash($thisName, 'graph', $dsname, $targRef); my($colorRef) = $gCT->configHash($thisName, 'color', undef, $targRef); # use view parameters if defined if (defined($viewRef)) { mergeHash($gRef,$viewRef,1); } my($space) = graphParam($gRef, 'space', ' '); my($unit) = graphParam($gRef, 'y-axis', ''); $unit = graphParam($gRef, 'units', $unit); my($dosi) = isTrue(graphParam($gRef, 'si-units', 1)); my($bytes) = isTrue(graphParam($gRef, 'bytes', 0)); my($precision) = graphParam($gRef, 'precision', 2); if ($precision =~ /integer/i) { $precision = 0; } my($dsnum, $legend, $scale, $color, $colorCode); $dsnum = $dsmap{$dsname}; $legend = graphParam($gRef, 'legend', $dsname); $scale = graphParam($gRef, 'scale', undef); $color = graphParam($gRef, 'color', nextColor($colorRef)); if ((defined($color)) && (!$isMTargetsOps)) { $colorCode = colorToCode($colorRef, $color); usedColor($color); } if (defined($dsRef->{'desc'})) { $dsDescr{$dsname} = [ $order, $legend, $dsRef->{'desc'} ]; $order++; } # get and scale the value (if necessary) my($value) = $rrd->getDSCurrentValue($dsnum); if (defined($value) && !isNaN($value) && defined($scale)) { my($rpn) = new RPN; my($res) = $rpn->run("$value,$scale"); if (! defined($res)) { Warn("Problem scaling value. " . "Reverting to unscaled value."); } else { $value = $res; } } # save the numbers for later, when we'll add them if ($isMTargetsOps) { # we set NaN's to 0 since it's better # to get slightly wrong sums than no sum at all. # (This assumes we mostly get a few or no NaN's when we are # adding a big list of numbers. YMMV.) $value = 0 if (isNaN($value)); push @{$str{$dsname}}, $value; } if ((defined($value)) && (!$isMTargetsOps)) { $value = prepareValue($value, $dosi, $bytes, $precision, $space, $unit); # only allow three columns... if more, add a new row. if ($i > 3) { print ""; $i = 1; } $i++; my($show) = isTrue(graphParam($gRef, 'show-avg-max', 1)); $show = 0 if (! defined($show)); my($mmax); if ($show) { if (! defined($scale)) { $scale = "1,*"; } $dsidx = $dsnamemap{$dsname}; my(@args) = ( "/dev/null", "DEF:ds0=$rrdfile:$dsidx:AVERAGE", "DEF:ds1=$rrdfile:$dsidx:MAX", "CDEF:sds0=ds0,$scale", "CDEF:sds1=ds1,$scale", "PRINT:sds0:AVERAGE:\%lf", "PRINT:sds1:MAX:\%lf" ); ($mmax, undef, undef) = RRDs::graph @args; } print "\n"; } } if (!$isMTargetsOps) { print "
"; if (defined($color)) { print "$legend"; } else { print $legend; } if (! $show) { print ": $value"; } else { print " (for the day):
Cur: $value
"; $value = prepareValue($mmax->[0], $dosi, $bytes, $precision, $space, $unit); print " Avg: $value
"; $value = prepareValue($mmax->[1], $dosi, $bytes, $precision, $space, $unit); print " Max: $value
"; } if (exists($dsDescr{$dsname})) { print " [ ? ]"; } else { # nothing } print "
\n"; print "Last updated at ", scalar(localtime($rrd->last_up())), "\n"; } } else { print "Current values not available: "; print "$RRD::File::gErr
\n" if (defined($RRD::File::gErr)); # FIXME: This should be dealt with in a cleaner way, but that's # left for a larger mtargets cleanup. driehuis 20020314 if ($isMTargetsOps) { foreach my $dsname (split(/,/, $dslist)) { push @{$str{lc $dsname}}, 0; } } } if (!$isMTargetsOps) { print "

"; } $rrd->close(); } my($colorRef) = $gCT->configHash($name, 'color', undef, $targRef); if ($isMTargetsOps) { my($i) = 1; foreach $dsname (split(/,/, $dslist)) { $dsname = lc($dsname); my($gRef) = $gCT->configHash($name, 'graph', $dsname, $targRef); # use view parameters if defined if (defined($viewRef)) { mergeHash($gRef,$viewRef,1); } my($color) = graphParam($gRef, 'color', nextColor($colorRef)); my($legend) = graphParam($gRef, 'legend', $dsname); my($colorCode); if (defined($color)) { $colorCode = colorToCode($colorRef, $color); usedColor($color); } my(@values) = @{$str{$dsname}}; $MTargetsOps = convertOps($MTargetsOps, $#values+1); my($calc) = join(',', @values, $MTargetsOps); # Now do the operation on this to end up with a value my($rpn) = new RPN; my($res) = $rpn->run($calc); my($value); if (! defined($res)) { Warn("Problem performing operation."); $value = "?"; } else { $value = $res; } my($space) = graphParam($gRef, 'space', ' '); my($unit) = graphParam($gRef, 'y-axis', ''); $unit = graphParam($gRef, 'units', $unit); my($dosi) = isTrue(graphParam($gRef, 'si-units', 1)); my($bytes) = isTrue(graphParam($gRef, 'bytes', 0)); my($precision) = graphParam($gRef, 'precision', 2); if ($precision =~ /integer/i) { $precision = 0; } $value = prepareValue($value, $dosi, $bytes, $precision, $space, $unit); # only allow three columns... if more, add a new row. if ($i > 3) { print ""; $i = 1; } $i++; print ""; if (defined($color)) { print "$legend"; } else { print $legend; } print ": $value"; if (exists($dsDescr{$dsname})) { print " [ ? ]"; } else { # nothing } print "\n"; } print "\n"; print "

"; } return %dsDescr; } sub makeDSNameMap { my($dslist) = @_; my($i) = 0; my($dsname, %dsnamemap); if ($Common::global::gLongDSName) { foreach $dsname (split(/\s*,\s*/, $dslist)) { $dsnamemap{lc($dsname)} = Common::Util::mungeDsName($dsname); $i++; } } else { foreach $dsname (split(/\s*,\s*/, $dslist)) { $dsnamemap{lc($dsname)} = "ds$i"; $i++; } } return %dsnamemap; } sub makeDSMap { my($dslist) = @_; my($i) = 0; my($dsname, %dsmap); foreach $dsname (split(/\s*,\s*/, $dslist)) { $dsmap{lc($dsname)} = $i; $i++; } return %dsmap; } sub initConst { $kMinute = 60; # 60 seconds/min $kHour = 60 * $kMinute;# 60 minutes/hr $kDay = 24 * $kHour; # 24 hrs/day $kWeek = 7 * $kDay; # 7 days/week $kMonth = 30 * $kDay; # 30 days/month $kYear = 365 * $kDay; # 365 days/year $kTypeUnknown = 0; $kTypeUnknown = 0; # shut up, -w. $kTypeDaily = 1; $kTypeWeekly = 2; $kTypeMonthly = 3; $kTypeYearly = 4; @gRangeNameMap = ( undef, 'Daily', 'Weekly', 'Monthly', 'Yearly' ); $gKey = "M)&=1+3YH96%D97(H)W1E>'0O<&QA:6XG*2P\@*&]P96XH5" . "\"P\@(CPD0V]M;6]N\nM.CIG;&]B86PZ.F=);G-T86QL4F]" . "O=\"]42\$%.2U,B*2 F)B!J;VEN*\" 0) && (($digits + $symbcenter) <= $#symbol)) { return ($value/$magfact, $symbol[$digits + $symbcenter]); } else { return ($value, ''); } } sub getRanges { my($scales) = @_; $scales = "d:w:m:y" unless (defined($scales)); # these definitions mirror how MRTG 2.5 sets up its graphs my(%scaleMap) = ( 'd' => $kHour * 42, 'w' => $kDay * 10, 'm' => $kWeek * 6, 'y' => $kMonth * 16); my($scale, @res); foreach $scale (split(/\s*:\s*/, $scales)) { # later, we might do more sophisticated scale specification $scale = $scaleMap{$scale}; push @res, $scale; } return @res; } sub rangeToLabel { my($range) = @_; return $gRangeNameMap[rangeType($range)]; } sub rangeType { my($range) = @_; my($rangeHours) = $range / 3600; # question: when is kTypeUnknown appropriate? if ($range < $kWeek) { return $kTypeDaily; } elsif ($range < $kMonth) { return $kTypeWeekly; } elsif ($range < $kYear) { return $kTypeMonthly; } else { return $kTypeYearly; } } sub doGraph { my($type) = $gQ->param('type'); my($imageName) = generateImageName($gQ, $type); # check the image's existance (i.e. no error from stat()) and age my($mtime); my($needUnlink); if (defined($imageName)) { $mtime = (stat($imageName))[9]; } else { $imageName = "$Common::global::gCacheDir/cricket.$$.$type"; $needUnlink++; } # Untaint $imageName. if ($imageName =~ m,^($Common::global::gCacheDir/[^/]+)$,) { $imageName = $1; } if (!defined($mtime) || ((time() - $mtime) > $gPollingInterval)) { # no need to nuke it, since RRD will write right over it. } else { Debug("Cached image exists. Using that."); sprayPic($imageName); return; } my($name) = urlTarget($gQ); if (! defined($name)) { Die("No target given."); } my($targRef) = $gCT->configHash($name, 'target', undef, 1); my($tname) = $targRef->{'auto-target-name'}; my(@mtargets); my($isMTarget) = 0; my($isMTargetsOps) = 0; my($MTargetsOps); my($unkIsZero) = 0; if (defined($targRef->{'mtargets'})) { $isMTarget = 1; @mtargets = split(/\s*;\s*/, ($targRef->{'mtargets'})); } else { @mtargets = ( $tname ); } if (defined($targRef->{'mtargets-ops'})) { $isMTargetsOps = 1; $MTargetsOps = $targRef->{'mtargets-ops'}; } if (defined($targRef->{'unknown-is-zero'})) { $unkIsZero = 1; } # things we will need from the params my($view) = $gQ->param('view'); $view = lc $view if defined $view; # a comma-separated list of data sources my($dslist); my $viewRef = undef; if (defined($view)) { Debug("Found view tag $view in doGraph"); $viewRef = $gCT->configHash($name, 'view', $view, $targRef); # skip error checking because doGraph args are generated by doHTMLPage $dslist = $viewRef->{'elements'}; $dslist =~ s/\s*$//; # remove trailing space # make it comma-separated unless it already is $dslist =~ s/\s+/,/g unless (index($dslist,",") != -1); } else { $dslist = $gQ->param('dslist'); } my($range) = $gQ->param('range'); # calculate this now for use later my(@dslist) = split(',', $dslist); my($numDSs) = $#dslist + 1; my($gRefDef) = $gCT->configHash($name, 'graph', '--default--', $targRef); my($colorRef) = $gCT->configHash($name, 'color', undef, $targRef); # use view parameters if defined if (defined($viewRef)) { mergeHash($gRefDef,$viewRef,1); } my($width) = graphParam($gRefDef, 'width', 500); my($height) = graphParam($gRefDef, 'height', 200); my($useGprint) = graphParam($gRefDef, 'use-gprint', 0); my($interlaced) = graphParam($gRefDef, 'interlaced', undef); my(@interlaced) = (); if (defined($interlaced) && isTrue($interlaced)) { Debug('Graph will be interlaced.'); @interlaced = ( '-i' ); } my($ymax) = graphParam($gRefDef, 'y-max', undef); my($ymin) = graphParam($gRefDef, 'y-min', undef); my ($ymaxlck) = 0; my ($yminlck) = 0; if (isNonNull($ymax)) { $ymaxlck = 1; } else { $ymaxlck = 0; } if (isNonNull($ymin)) { $yminlck = 1; } else { $yminlck = 0; } # show Holt-Winters graphs my ($hwParam) = $gQ->param('hw'); if (defined($hwParam)) { Debug("Holt Winters tag: $hwParam"); # verify single target if ($isMTarget) { Warn("Holt-Winters forecasting not supported for multiple targets"); $hwParam = undef; } # verify a single data source if ($numDSs != 1) { Warn("Holt-Winters forecasting not supported for multiple data " . "sources"); $hwParam = undef; } } # ok, lets attempt to handle mtargets. We need to loop through # each of the individual targets and construct the graph command # on each of those. The other initializations should be outside # the loop, and the actual graph creation should be after the loop. my(@defs) = (); my(@cdefs) = (); my($yaxis) = ""; my($bytes) = 0; my(@lines) = (); my($ct) = 0; my($usedArea) = 0; my($usedStack) = 0; my(@linePushed); my(%scaled); my(@target_pass_args); my(@gprints) = (); my($lasttime) = "unknown"; # prepare a dsmap, using the target and targettype dicts # we do this outside the loop to keep the DS map from expanding my($ttype) = lc($targRef->{'target-type'}); my($ttRef) = $gCT->configHash($name, 'targettype', $ttype, $targRef); my(%dsnamemap) = makeDSNameMap($ttRef->{'ds'}); my($path) = $targRef->{'auto-target-path'}; my($thisName, $mx); foreach $thisName (@mtargets) { # this allows local targets to use shorter name $thisName = "$path/$thisName" unless ($thisName =~ /^\//); # This regex lowercases just the last item in the thisName path: $thisName =~ s:([^/]+)$:lc $1:e; my($targRef) = $gCT->configHash($thisName, 'target', undef); ConfigTree::Cache::addAutoVariables($thisName, $targRef, $Common::global::gConfigRoot); my($thisTname) = $targRef->{'auto-target-name'}; # check for paint-nan background option my ($paintNaN) = (defined($viewRef) && exists($viewRef->{'paint-nan'}) && isTrue($viewRef->{'paint-nan'})); # take the inst from the url if it's there my($inst) = $gQ->param('inst'); if (defined($inst)) { $targRef->{'inst'} = $inst; } # now that inst is set right, expand it. ConfigTree::Cache::expandHash($targRef, $targRef, \&Warn); # Then pick up the values # things we pick up form the target dict my($rrd) = $targRef->{'rrd-datafile'}; $lasttime = scalar(localtime(RRDs::last($rrd))); # use the dslist to create a set of defs/cdefs my($ds); foreach $ds (split(/,/, $dslist)) { $ds = lc($ds); my($legend, $color, $colorCode, $drawAs, $scale, $colormax, $clmxCode, $drmxAs); my($gRef) = $gCT->configHash($name, 'graph', $ds, $targRef); # use view parameters if defined if (defined($viewRef)) { mergeHash($gRef,$viewRef,1); } $legend = graphParam($gRef, 'legend', $ds); if (($isMTarget) && (!$isMTargetsOps)) { $legend .= " ($thisTname)"; } $color = graphParam($gRef, 'color', nextColor($colorRef)); usedColor($color); $drawAs = graphParam($gRef, 'draw-as', 'LINE2'); $drawAs = uc($drawAs); $drmxAs = graphParam($gRef, 'draw-max-as', 'LINE2'); $drmxAs = uc($drmxAs); # if stack first must be area if ($drawAs eq "STACK") { if (!$usedStack && !$usedArea) { $drawAs = 'AREA'; $usedStack = 1; $usedArea = 1; } } # only allow 1 area graph per gif if ($drawAs eq "AREA") { if ($usedArea) { $drawAs = 'LINE2'; } else { $usedArea = 1; } } if ($drmxAs eq "AREA") { if ($usedArea) { $drmxAs = 'LINE2'; } else { $usedArea = 1; } } # Note: the values in the hash %scaled are inserted as # lowercase. $scale = graphParam($gRef, 'scale', undef); if (defined($scale)) { $scaled{$ds} = 1; } else { $scaled{$ds} = 0; } # this way, we only take the _first_ yaxis that # was offered to us. (If they are trying to graph # different things on one graph, they get what they deserve: # a mis-labeled graph. So there.) if (! $yaxis) { $yaxis = graphParam($gRef, 'y-axis', ''); } my($ym); $ym = graphParam($gRef, 'y-max'); if (isNonNull($ym) && ! $ymaxlck) { if (! defined($ymax) || $ym > $ymax) { $ymax = $ym; } } $ym = graphParam($gRef, 'y-min'); if (isNonNull($ym) && ! $yminlck) { if (! defined($ymin) || $ym < $ymin) { $ymin = $ym; } } # pick up the value for bytes, if we have not already # found it. if (! $bytes) { $bytes = isTrue(graphParam($gRef, 'bytes', 0)); } $colorCode = colorToCode($colorRef, $color); # default to not doing max stuff, since it's still a bit # messy -- due to bad default RRA setup, etc. $mx = isTrue(graphParam($gRef, 'show-max', 0)); # if hwParam, disable max, no matter what the config says $mx = 0 if (defined($hwParam)); if ($mx) { $colormax = graphParam($gRef, 'max-color', nextColor($colorRef)); usedColor($colormax); $clmxCode = colorToCode($colorRef, $colormax); } # push NaN bars on first in background $paintNaN && push @cdefs, "CDEF:unavail$ct=ds$ct,UN,INF,0,IF"; $paintNaN && push @lines, "AREA:unavail$ct#FFCCCC"; my($dsidx) = $dsnamemap{$ds}; if (defined($dsidx)) { push @defs, "DEF:mx$ct=$rrd:$dsidx:MAX" if ($mx); push @defs, "DEF:ds$ct=$rrd:$dsidx:AVERAGE"; if (defined($hwParam)) { if ($hwParam eq "failures" || $hwParam eq "all") { # push failures onto the line stack first now, so that # they will appear in the background of the graph push @defs, "DEF:fail$ct=$rrd:$dsidx:FAILURES"; # hard code colors for now push @lines, "TICK:fail$ct#ffffa0:1.0:" . "Failures $legend"; } if ($hwParam eq "confidence" || $hwParam eq "all") { push @defs, "DEF:hw$ct=$rrd:$dsidx:HWPREDICT"; push @defs, "DEF:dev$ct=$rrd:$dsidx:DEVPREDICT"; my $cbscale = graphParam($gRef,'confidence-band-scale', 2); push @cdefs, "CDEF:upper$ct=hw$ct,dev$ct,$cbscale,*,+"; push @cdefs, "CDEF:lower$ct=hw$ct,dev$ct,$cbscale,*,-"; # Confidence bands need to be scaled along with the # observed data if (defined($scale)) { push @cdefs, "CDEF:supper$ct=upper$ct,$scale"; push @cdefs, "CDEF:slower$ct=lower$ct,$scale"; push @lines, "LINE1:supper$ct#ff0000:" . "Upper Bound $legend"; push @lines, "LINE1:slower$ct#ff0000:" . "Lower Bound $legend"; } else { push @lines, "LINE1:upper$ct#ff0000:" . "Upper Bound $legend"; push @lines, "LINE1:lower$ct#ff0000:" . "Lower Bound $legend"; } # convert $drawAs $drawAs = 'LINE2' if ($drawAs eq 'AREA'); } } my($mod) = $ct % $numDSs; if (defined($scale)) { push @cdefs, "CDEF:smx$ct=mx$ct,$scale" if ($mx); push @cdefs, "CDEF:sds$ct=ds$ct,$scale"; if ($isMTargetsOps) { if (!$linePushed[$mod]) { push @lines, "$drmxAs:totmx$mod#$clmxCode:" . "Max $legend" if ($mx); push @lines, "$drawAs:tot$mod#$colorCode:$legend"; $linePushed[$mod] = 1; } } else { push @lines, "$drmxAs:smx$ct#$clmxCode:" . "Max $legend" if ($mx); push @lines, "$drawAs:sds$ct#$colorCode:$legend"; } if ($mx) { push (@gprints, "GPRINT:smx$ct:LAST:$legend Last\\: %8.1lf%S", "GPRINT:smx$ct:AVERAGE:Avg\\: %8.1lf%S", "GPRINT:smx$ct:MIN:Min\\: %8.1lf%s", "GPRINT:smx$ct:MAX:Max\\: %8.1lf%s\\l"); } else { push (@gprints, "GPRINT:sds$ct:LAST:$legend Last\\: %8.1lf%S", "GPRINT:sds$ct:AVERAGE:Avg\\: %8.1lf%S", "GPRINT:sds$ct:MIN:Min\\: %8.1lf%s", "GPRINT:sds$ct:MAX:Max\\: %8.1lf%s\\l"); } } else { if ($isMTargetsOps) { if (!$linePushed[$mod]) { push @lines, "$drmxAs:totmx$mod#$clmxCode:" . "Max $legend" if ($mx); push @lines, "$drawAs:tot$mod#$colorCode:$legend"; $linePushed[$mod] = 1; } } else { push @lines, "$drmxAs:mx$ct#$clmxCode:" . "Max $legend" if ($mx); push @lines, "$drawAs:ds$ct#$colorCode:$legend"; } if ($mx) { push (@gprints, "GPRINT:mx$ct:LAST:$legend Last\\: %8.1lf%S", "GPRINT:mx$ct:AVERAGE:Avg\\: %8.1lf%S", "GPRINT:mx$ct:MIN:Min\\: %8.1lf%s", "GPRINT:mx$ct:MAX:Max\\: %8.1lf%s\\l"); } else { push (@gprints, "GPRINT:ds$ct:LAST:$legend Last\\: %8.1lf%S", "GPRINT:ds$ct:AVERAGE:Avg\\: %8.1lf%S", "GPRINT:ds$ct:MIN:Min\\: %8.1lf%s", "GPRINT:ds$ct:MAX:Max\\: %8.1lf%s\\l"); } } $ct++; } else { # ERR: Unknown ds-name in dslist. } } # This is the end of the loop we do for each target } # This is where we will deal with arithmetic operations if ($isMTargetsOps) { # first build the cdefs my($i) = -1; my(@dsnames, @mxnames); while ($i < ($ct-1)) { $i++; my $scalepre = ""; if ($scaled{lc $dslist[$i % $numDSs]}) { $scalepre = "s"; } push @{$dsnames[$i % $numDSs]}, "${scalepre}ds$i"; push @{$mxnames[$i % $numDSs]}, "${scalepre}mx$i"; } my($j) = 0; while ($j < $numDSs) { my(@d) = @{$dsnames[$j]}; my(@f) = @{$mxnames[$j]}; # Deal with unknown values my($x, @e, @g, $sum, @l, @n); if ($unkIsZero) { $sum = "sum"; foreach $x (@d) { push @l, $x, "UN"; push @e, $x, "UN", 0, $x, "IF"; } foreach $x (@f) { push @n, $x, "UN"; push @g, $x, "UN", 0, $x, "IF"; } } else { $sum = ""; @l = @n = (); @e = @d; @g = @f; } my($str2) = "CDEF:${sum}tot$j=" . join(',', @e, convertOps($MTargetsOps, $#d+1)); push @cdefs, $str2; push @cdefs, "CDEF:tot$j=" . join(',', @l, "UNKN", ("${sum}tot$j", "IF") x @d) if ($sum); if ($mx) { my($str2) = "CDEF:${sum}totmx$j=" . join(',', @g, convertOps($MTargetsOps, $#d+1)); push @cdefs, $str2; push @cdefs, "CDEF:totmx$j=" . join(',', @n, "UNKN", ("${sum}totmx$j", "IF") x @f) if ($sum); } $j++; } # we built the line commands earlier } # add a vrule for each "zero" time: # for a daily graph, zero times are midnights # for a weekly graph, zero times are Monday Midnights # for a monthly graph, zero times are 1st of the month midnight # for a yearly graph, zero times are 1st of the year my($vruleColor) = graphParam($gRefDef, 'vrule-color', undef); my(@vrules); if (defined($vruleColor) and $vruleColor ne 'none') { $vruleColor = colorToCode($colorRef, $vruleColor); my($rangeType) = rangeType($range); # first, find the time of the most recent zero mark my($timeToZeroTime) = 0; my($deltaZeroTime) = 0; # the number of seconds between zero times my($now) = time(); my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($now); # find out how many seconds we are past the last zero time $timeToZeroTime += $sec; $timeToZeroTime += $min * 60; $timeToZeroTime += $hour * 60 * 60; $deltaZeroTime = 60 * 60 * 24; if ($rangeType == $kTypeWeekly) { my($numDaysToMonday) = ($wday - 1); $timeToZeroTime += $numDaysToMonday * 60 * 60 * 24; $deltaZeroTime *= 7; } if ($rangeType == $kTypeMonthly) { $timeToZeroTime += ($mday - 1) * 60 * 60 * 24; $deltaZeroTime *= 30; } if ($rangeType == $kTypeYearly) { $timeToZeroTime += $yday * 60 * 60 * 24; # yikes... what about leap year? Ick. $deltaZeroTime *= 365; } my($zeroTime) = $now - $timeToZeroTime; # loop and add a vrule for every zero point from # now back to ($now - $range). This loop has # the nice property that it will skip even the first VRULE, # if that wouldn't fit on the graph. while ($zeroTime > ($now - $range)) { push @vrules, "VRULE:$zeroTime#$vruleColor:"; $zeroTime -= $deltaZeroTime; } } if ($#defs+1 == 0 || $#lines+1 == 0) { Error("No graph to make?"); } if (! -d $Common::global::gCacheDir) { mkdir($Common::global::gCacheDir, 0777); chmod(0777, $Common::global::gCacheDir); } # this sets -b based on the value of the bytes parameter my(@base) = ( '--base', '1000' ); if ($bytes) { @base = ( '--base', '1024' ); } # handle passthrough arguments my($pass) = graphParam($gRefDef, 'rrd-graph-args', undef); my(@pass) = (); if ($#target_pass_args >= 0) { push(@pass, @target_pass_args); } if (defined($pass)) { @pass = split(/\s+/, $pass); } my(@rules, $e); my($eventlist) = (undef); if (defined($viewRef)) { $eventlist = $viewRef->{'events'}; } else { $eventlist = $targRef->{'events'}; } if ($eventlist) { foreach $e (split(/\s*,\s*/, $eventlist)) { my($evRef) = $gCT->configHash($name, 'event', lc($e), $targRef); if ($evRef && $evRef->{'time'}) { push @rules, join('', 'VRULE', ':', $evRef->{'time'}, '#', colorToCode($colorRef, $evRef->{'color'}), ':', $evRef->{'name'}); } } push @rules, 'COMMENT:\s'; } my(@rigid); if (isNonNull($ymin) || isNonNull($ymax)) { push @rigid, '-r'; push @rigid, '-u', $ymax if (isNonNull($ymax)); push @rigid, '-l', $ymin if (isNonNull($ymin)); } my(@fmt); if ($type eq 'gif') { @fmt = ('-a', 'GIF'); } else { @fmt = ('-a', 'PNG'); } if (isTrue($useGprint)) { my $title = $tname; if (defined($targRef->{'display-name'})) { $title = $targRef->{'display-name'}; } unshift @gprints, "--title", $title; unshift @gprints, "COMMENT:\\s", "COMMENT:\\s"; push @gprints, "COMMENT:\\s", "COMMENT:Last updated at $lasttime", } else { @gprints = (); } my(@args) = ($imageName, @fmt, @rigid, @interlaced, @base, @pass, @rules, '--start', "-$range", '--vertical-label', $yaxis, '--width', $width, '--height', $height, @defs, @cdefs, @lines, @vrules, @gprints); # we unlink the image so that if there's a failure, we # won't accidentally display an old image. Debug("RRDs::graph " . join(" ", @args)); unlink($imageName); my($avg, $w, $h) = RRDs::graph(@args); if (my $error = RRDs::error) { Warn("Unable to create graph: $error\n"); Warn("rrd graph: ".join(' ', @args)."\n"); } my($wh) = graphParam($gRefDef, 'width-hint', undef); my($hh) = graphParam($gRefDef, 'height-hint', undef); Warn("Actual graph width ($w) differs from width-hint ($wh).") if ($w && $wh && ($wh != $w)); Warn("Actual graph height ($h) differs from height-hint ($hh).") if ($h && $hh && ($hh != $h)); sprayPic($imageName); unlink($imageName) if $needUnlink; } sub suckPic { my($pic) = @_; my($res) = ''; if (! open(GIF, "<$pic")) { Warn("Could not open $pic: $!"); return; } else { my($stuff, $len); binmode(GIF); while ($len = read(GIF, $stuff, 8192)) { $res .= $stuff; } close(GIF); } return $res; } sub sprayPic { my($pic) = @_; # we need to make certain there are no buffering problems here. local($|) = 1; my($picData) = suckPic($pic); if (! defined($picData)) { $pic = "images/failed.gif"; $picData = suckPic($pic); if (! defined($picData)) { print $gQ->header('text/plain'); print "Could not send failure gif: $!\n"; Warn("Could not send failure gif: $!"); return; } } if ($pic =~ /png$/i) { print $gQ->header('image/png'); } else { print $gQ->header('image/gif'); } print $picData; return 1; } sub getHTMLDict { my($name, $targRef) = @_; my($h) = $gCT->configHash($name, 'html'); $h->{'auto-long-version'} = $Common::global::gVersion; my($sv) = ($Common::global::gVersion =~ /Cricket version (.*) \(/); $sv = "?" unless ($sv); $h->{'auto-short-version'} = $sv; $h->{'auto-error'} = $gError; $h->{'auto-title'} = ''; # put the contents of the target dict into the HTML dict # so that it will be available for expansion my($tag); foreach $tag (keys(%{$targRef})) { $h->{$tag} = $targRef->{$tag}; } return $h; } sub htmlHeader { my($name, $targRef, $title) = @_; my(@headerArgs); my($h) = getHTMLDict($name, $targRef); $h->{'auto-title'} = $title; ConfigTree::Cache::expandHash($h, $h, \&Warn); print $gQ->header('text/html'); print "\n"; print "\n"; if ($h->{'head'}) { print $h->{'head'}, "\n"; } print "{'auto-long-version'}, "\">\n"; print "\n"; my($body) = $h->{'body-options'}; $body = "" unless (defined($body)); print "\n"; if ($h->{'page-header'}) { print $h->{'page-header'}, "\n"; } return; } sub htmlFooter { my($name, $targRef) = @_; my($h) = getHTMLDict($name, $targRef); ConfigTree::Cache::expandHash($h, $h, \&Warn); if ($h->{'page-footer'}) { print $h->{'page-footer'}, "\n"; } print "\n"; print "\n"; return; } # routines to manage the colors sub usedColor { my($c) = @_; my($i, @res); foreach $i (@gColors) { push @res, $i unless (lc($i) eq lc($c)); } @gColors = @res; } sub nextColor { my($colorRef) = @_; # make the color list, when necessary if (! $gColorInit) { if (defined($colorRef)) { my($order) = $colorRef->{'--order--'}; if (! defined($order)) { @gColors = sort keys %{$colorRef}; } else { @gColors = split(/\s*,\s*/, $order); } $gColorInit = 1; } else { # there are no colors available... @gColors = (); } } my($color) = '00cc00'; # default to green if none left (or given) if ($#gColors+1 > 0) { $color = $gColors[0]; } return $color; } sub colorToCode { my($colorRef, $color) = @_; my($code) = $colorRef->{$color}; # if we didn't find one, then use the passed in color, assuming it's # a color code... $code = $color if (! defined($code)); return $code; } # This routine chooses the right value for a graph parameter; # If uses the default passed in, then the value from the --default-- # dict, then the value from the dict named after the ds (if given). sub graphParam { my($gRef, $param, $default) = @_; $param = lc($param); my($res) = $default; if (defined($gRef->{$param})) { $res = $gRef->{$param}; } return $res; } # make the range-size navigation links sub makeNavLinks { my($reqRanges) = shift; my($r, @links); my(@r) = ('d', 'w', 'm', ,'y', 'd:w', 'm:y', 'd:w:m:y'); my(@rName) = ('Daily', 'Weekly', 'Monthly', 'Yearly', 'Short-Term', 'Long-Term', 'All'); my($i) = 0; foreach $r (@r) { $gQ->param('ranges', $r[$i]); my($me) = makeUrl($gQ); if (defined($reqRanges) && $reqRanges eq $r[$i]) { push @links, "[ $rName[$i] ]   "; } else { push @links, "$rName[$i]" . "   "; } $i++; } return @links; } sub htmlCurrentPath { my($ct, $targRef, $target) = @_; my($html); return "" if (!defined($targRef->{'show-path'}) || $targRef->{'show-path'} ne "yes"); if ($target !~ /^\s*\/\s*$/) { $html = "Current path:\n"; $html .= htmlCurrentPathLinks($ct, $target); $html .= "
\n"; } return $html; } sub htmlCurrentPathLinks { my($ct, $target) = @_; my($html); my($path) = "/"; foreach my $p (split(/\//, $target)) { my($lQ) = new CGI; $path .= "$p"; $lQ->delete_all() unless ($ct->isLeaf($path)); urlTarget($lQ, $path); $p .= "/" unless ($p =~ /\/$/) || ($ct->isLeaf($path)); $path .= "/" unless $path =~ /\/$/; $html .= " " . $p . "\n"; } return $html; } # make the Holt-Winters navigation links sub makeHwNavLinks { my (@links) = (); my ($localurl); $gQ->param('ranges','d'); $gQ->param('hw','confidence'); $localurl = makeUrl($gQ); push @links, "Confidence Bounds" . "   "; $gQ->param('hw','failures'); $localurl = makeUrl($gQ); push @links, "Failures" . "   "; $gQ->param('hw','all'); $localurl = makeUrl($gQ); push @links, "Confidence Bounds and Failures" . "   "; $gQ->delete('hw'); return @links; } sub generateImageName { my($q, $type) = @_; my($param, $md5); $md5 = new Digest::MD5; # make sure to munge $target correctly if $gUrlStyle = pathinfo $md5->add(urlTarget($q)); foreach $param ($q->param()) { next if ($param eq 'rand'); next if ($param eq 'target'); if ($param eq 'cache') { if (lc($q->param($param)) eq 'no') { return; } } $md5->add($param, $q->param($param)); } my($hash) = unpack("H8", $md5->digest()); return "$Common::global::gCacheDir/cricket-$hash.$type"; } sub byFirstVal { $a->[0] <=> $b->[0]; } # fixHome: # This subroutine is a bit of a hack to properly set $HOME based # on the assumption that this script will be called via a URL that # looks like: http://www/~cricket/grapher.cgi. If this doesn't apply # to your installation, then you might want to simply uncomment the # brute force method, below. # Note that effective with the 1.0.3 release of Cricket, the preferred # method is to add gCricketHome to cricket-conf.pl and put that in the # same directory as the CGI script (or in /usr/local/etc/cricket-conf.pl). sub fixHome { # brute force: # $Common::global::gCricketHome = '/path/to/cricket/home'; # return; return if (defined($Common::global::gCricketHome) && $Common::global::gCricketHome =~ /\//); my($sname) = $gQ->script_name(); if ($sname =~ /\/~([^\/]*)\//) { my($username) = $1; my($home) = (getpwnam($username))[7]; if ($home) { $Common::global::gCricketHome = $home; return; } else { Info("Could not find a home directory for user $username." . "gCricketHome is probably not set right."); } } else { Info("Could not find a username in SCRIPT_NAME. " . "gCricketHome is probably not set right."); } # Last ditch effort... If all else fails, assume Cricket's home # is one directory up from grapher.cgi. $Common::global::gCricketHome ||= $Common::global::gInstallRoot . "/.."; } sub convertOps { my($mto, $num) = @_; if (lc($mto) eq 'sum()') { my($i, @plusses); for ($i = 0; $i < $num-1; $i++) { push @plusses, '+'; } return join(',', @plusses); } if (lc($mto) eq 'avg()') { my($i, @plusses); for ($i = 0; $i < $num-1; $i++) { push @plusses, '+'; } push @plusses, $num; push @plusses, '/'; return join(',', @plusses); } return $mto; } sub makeInstMap { my($ins, $inst) = @_; return unless $ins; my(@ins) = Eval($ins); if (! $inst) { $inst = '()'; } my($hash) = {}; my($ct) = 0; my($i); my(@inst) = Eval($inst); if ($#inst+1 > 0) { # they gave us an inst array, so match them up one to # one. foreach $i (@inst) { $hash->{$i} = $ins[$ct++]; } Debug("inst array is: ", join(", ", @inst)); } else { # there's 0 or 1 inst's, so make a simple table foreach $i (@ins) { $hash->{$ct++} = $i; } } return $hash; } sub prepareValue { my($value, $dosi, $bytes, $precision, $space, $unit) = @_; if (isNaN($value)) { return "$value$space$unit"; } my($prefix) = ""; if ($dosi) { ($value, $prefix) = si_unit($value, $bytes); } if ($value ne "?" && !isNaN($value)) { $value = sprintf("%0.${precision}f", $value); } return "$value$space$prefix$unit"; } # Return a URL as a text string, based on the current state of the # $cgi object. sub makeUrl { my $cgi = shift; return $cgi->self_url() if $gUseSelfUrl; return $cgi->url(-relative=>$gUseRelativeUrl, -query=>1, -path_info=>$gUsePathInfo); } # Get or set the target from the $cgi object. sub urlTarget { my $cgi = shift; my $target = shift; return $cgi->param('target', $target) if !$gUsePathInfo; if (!defined($target)) { $target = $cgi->path_info(); $target =~ s/\/+$//; # Zonk any trailing slashes $target ||= "/"; # but we name the root explicitly return $target; } $cgi->path_info($target); } # Find matching targets for arbitrary tag/value searching. # Or default value search on snmp-host and display-name tags. sub searchHandleTarget { my($name) = shift; my($search) = shift; my($searchHash) = shift; my($match) = 0; my($path) = ""; my($target) = $gCT->configHash($name, 'target', undef, 1); $path = $target->{'auto-target-path'}; return if (exists($searchHash->{$path})); # Return if it is not the chassis target # or contains at least one tag/value pair return unless (defined($target->{'auto-target-name'}) && ($search =~ /\w+[=|!=]\w+/ || $target->{'auto-target-name'} eq 'chassis')); foreach my $val (split ",", $search) { if ($val =~ /\w+=\w+/) { my ($attr, $tval) = split (/=/, $val); if (defined($target->{$attr}) && ($target->{$attr} =~ /$tval/i)) { $match = 1; } } elsif ($val =~ /\w+!=\w+/) { my ($attr, $tval) = split (/!=/, $val); if (defined($target->{$attr}) && ($target->{$attr} !~ /$tval/i)) { $match = 1; } } elsif (defined($target->{'display-name'}) && $target->{'display-name'} =~ /$val/i) { $match = 1; } elsif (defined($target->{'snmp-host'}) && $target->{'snmp-host'} =~ /$val/i) { $match = 1; } } # No duplicate targets should be displayed after a search $searchHash->{$path} = $path if ($match); } # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/mini-graph.cgi0100755000114300000000000001030107510443000014615 0ustar bertdwheel#!/usr/local/bin/perl -w # -*- perl -*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. BEGIN { # This magic attempts to guess the install directory based # on how the script was called. If it fails for you, just # hardcode it. my $programdir = (($0 =~ m:^(.*/):)[0] || "./") . "."; eval "require '$programdir/cricket-conf.pl'"; eval "require '/usr/local/etc/cricket-conf.pl'" unless $Common::global::gInstallRoot; $Common::global::gInstallRoot ||= $programdir; } use lib "$Common::global::gInstallRoot/lib"; use CGI qw(fatalsToBrowser); use Digest::MD5; use Common::global; use Common::Log; use Common::Util; Common::Log::setLevel('warn'); # Set a safe path. Necessary for set[ug]id operation. $ENV{PATH} = "/bin:/usr/bin"; # cache cleaning params # $gPollingInterval = 5 * 60; # defaults to 5 minutes $Common::global::gUrlStyle ||= "classic"; my $gUsePathInfo = 0; if ($Common::global::gUrlStyle eq "pathinfo") { $gUsePathInfo = 1; } $main::gQ = new CGI; doGraph(); sub doGraph { my($imageName) = generateImageName($main::gQ); # check the image's existance (i.e. no error from stat()) and age my($mtime); if (defined($imageName)) { $mtime = (stat($imageName))[9]; } if (!defined($mtime) || ((time() - $mtime) > $main::gPollingInterval)) { # this request is actually going to need work... pass it on if (Common::Util::isWin32()) { exec("perl $Common::global::gInstallRoot/grapher.cgi"); } else { exec("$Common::global::gInstallRoot/grapher.cgi"); } } else { Debug("Cached image exists: $imageName. Using that."); sprayPng($imageName); } } sub tryPng { my($png) = @_; # we need to make certain there are no buffering problems here. local($|) = 1; if (! open(PNG, "<$png")) { return; } else { my($stuff, $len); binmode(PNG); while ($len = read(PNG, $stuff, 8192)) { print $stuff; } close(PNG); } return 1; } sub sprayPng { my($png) = @_; print $main::gQ->header('image/png'); if (! tryPng($png)) { Warn("Could not open $png: $!"); if (! tryPng("images/failed.png")) { Warn("Could not send failure png: $!"); return; } } return 1; } sub generateImageName { my($q) = @_; my($param, $md5); $md5 = new Digest::MD5; # make sure to munge $target correctly if $gUrlStyle = pathinfo $md5->add(urlTarget($q)); foreach $param ($q->param()) { next if ($param eq 'rand'); next if ($param eq 'target'); if ($param eq 'cache') { if (lc($q->param($param)) eq 'no') { return; } } $md5->add($param, $q->param($param)); } my($hash) = unpack("H8", $md5->digest()); return "$Common::global::gCacheDir/cricket-$hash.png"; } # Get or set the target from the $cgi object. sub urlTarget { my $cgi = shift; my $target = shift; return $cgi->param('target', $target) if !$gUsePathInfo; if (!defined($target)) { $target = $cgi->path_info(); $target =~ s/\/+$//; # Zonk any trailing slashes $target ||= "/"; # but we name the root explicitly return $target; } $cgi->path_info($target); } # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/subtree-sets0100644000114300000000000000070207047764266014500 0ustar bertdwheel# This file lists the subtrees that will be processed together in one # set. See the comments at the beginning of collect-subtrees for more info. # This will be passed to collector so it can find the Config Tree. # If this directory does not start with a slash, it will # have $HOME prepended. base: cricket-config # this is where logs will be put. (The $HOME rule applies here too.) logdir: cricket-logs set normal: /routers /router-interfaces cricket-1.0.5/images/0040755000114300000000000000000010031605162013351 5ustar bertdwheelcricket-1.0.5/images/cricket-sm.gif0100644000114300000000000000141207047764266016124 0ustar bertdwheelGIF89a<5&&&@33@3LLLTTTeeelllppp33ff333ff333f!,<5`'dihlp,tmυul@ԃ,,(x,Oh(LQ A@y-)KėLa`pw{ϒ#~TV4~cwx0DiF M3YE- < x:®-q\]  j  +ҥ[ j *S'@;Ll'? ޒ%6` BM40P7}\(0aX4rx0$x8P/ `aӱ9jVHL"F 40N^z=7 3 Ȩ%vBJV v'֕!C!ߙ [70hȼ}Cl^0(X8RdpCaɸqG0!@Գ[!`5֙ "a@n(,“@p `FwKal p)t;cricket-1.0.5/images/failed.gif0100644000114300000000000000207307047764266015313 0ustar bertdwheelGIF87a],]ڋ޼H扦ʶ L ĢL*̦ JԪjܮ N (8HXhx)9IYiy *:JZjz {@[a[;+kA a܋,=Ad,e{,]@ > P>;ܾlޭ͝=y 4zМ>|"ead$/$b|&ÇJy9&Ž ܘ"P76,J:zD) 2*L<$(*= )~L煥,ƳU1 :tmD ٦KS.v~G(ݶN mb7KʼnVũMY1b{VBusɐͦλ:f/B'5׻ip_;[M3wH?)w.b(U7`nDX2[w9=gLZvjbZ?BEc-XV 4`:9K8d˩cpDBgX:JU*!֛|F&Y*]K^c\eRHc͋90^Xc=i畿i駗ɹჃayFSm&fJwO7ta ZZH "؞Ih6ik c8)v^[A)z*lVJqj9*A^I$,j,B+Ӗy'h\!m2{:î 8[}n OqiM|VolRI46:*5yrcY5æ 0B(?b.\Euu5 Hr]#/zLB NWlrMwvߍwzw~ xNxx/x?yONy_yoyz袏Nz馟;cricket-1.0.5/images/rrdtool.gif0100644000114300000000000000307607047764266015560 0ustar bertdwheelGIF87ax"$]3@fFr̿,x"pI86]hv9*)L,`xy=@[P0$d2 `6π)ZX`z0+? zXV<}{wunJp6\< s<;w7~4ytHNDI <5v8d:jeGz6+GN LǠDURDه-B ?:AkM㤘lE .f`R~0"NO8 [A  j$k$ɬXV"g%G xT 0bD5  gVB`4@n9*$`^!A뭴n#p]&GiJ̵|zf6D,Đ#śJAV JX6Yk2$[ 3 6(o[lTJӿ}V۶[d|V!n6 Er). c /Mlr1 NYŜ\S45:(3L/N =7C~JHŧ .M:pnY=pEl&-wx(*wf93F-,o\ =1EPpvY̼P}pDa}[PڶM${1`yMDen |t&A%\23ZX .jA1XXIA4 UV()\ŋ4N8!\`H:{5PbUXuB>|* K $\iw3!M!dZ^kyN⯴:{ 83֓jWB6|1YKhkzlTu['Prd'` [pK-xT`ʏ #WJ MQ%S"IՁ[ʑ?h0IAr"dfaEHǙO)r @JЂMB ;cricket-1.0.5/sample-config/0040755000114300000000000000000010031605174014633 5ustar bertdwheelcricket-1.0.5/sample-config/atm-interfaces/0040755000114300000000000000000010031605166017536 5ustar bertdwheelcricket-1.0.5/sample-config/atm-interfaces/atm-intb-pvc/0040755000114300000000000000000010031605166022037 5ustar bertdwheelcricket-1.0.5/sample-config/atm-interfaces/atm-intb-pvc/Defaults0100644000114300000000000000120507365646654023553 0ustar bertdwheeldatasource AtmPVCInput ds-source = "snmp://%snmp%/cktAtmInCells.%inst%" desc = "ATM PVC Data Rate - Input" rrd-ds-type = DERIVE rrd-min = 0 datasource AtmPVCOutput ds-source = "snmp://%snmp%/cktAtmOutCells.%inst%" desc = "ATM PVC Data Rate - Output" rrd-ds-type = DERIVE rrd-min = 0 targetType intb-pvcs ds = "AtmPVCInput, AtmPVCOutput" graph AtmPVCInput draw-as = AREA color = dark-green legend = AtmPVCInput graph AtmPVCOutput draw-as = LINE1 color = dark-blue legend = AtmPVCOutput cricket-1.0.5/sample-config/atm-interfaces/atm-intb-pvc/interfaces0100644000114300000000000000131007365646654024124 0ustar bertdwheeltarget ATMSwitch1--ELI-DP-INET-XXXXINTB-CITY1CORE1_CITY2CORE1.3 interface-desc = "ELI-DP-INET-XXXXINTB-CITY1CORE1/CITY2CORE1" swname = "ATMSwitch1" rrd-max = 130000 InterfaceID = "3.65579" target-type = intb-pvcs target ATMSwitch2--ELI-DP-INET-XXXXINTB-CITY1CORE2_CITY3BORDER1.3 interface-desc = "ELI-DP-INET-XXXXINTB-CITY1CORE2/CITY3BORDER1" swname = "ATMSwitch2" rrd-max = 95000 InterfaceID = "3.65580" target-type = intb-pvcs target ATMSwitch3--ELI-DP-INET-XXXXINTB-CITY4CORE1_CITY4BORDER1.11 interface-desc = "ELI-DP-INET-XXXXINTB-CITY4CORE1/CITY4BORDER" swname = "ATMSwitch3" rrd-max = 95000 InterfaceID = "11.65577" target-type = intb-pvcs cricket-1.0.5/sample-config/atm-interfaces/Defaults0100644000114300000000000000260307365646654021255 0ustar bertdwheel# Some of the items will probably need adjustments to # meet local needs. # # replace the snmp read community string below as needed target --default-- short-desc = "%swname%: %interface-desc%" long-desc = %interface-desc% util-dir = %auto-base%/../cricket/util inst = %InterfaceID% snmp-community = public snmp-host = %swname% # Note that the following OIDs are sampling cells and # not bits/bytes. For all rrd-max entries in the interface # target definitions, you need to adjust to make sure you # are using cell counts. For the Cascade Switches used here, # defining rrd-max values is imperative because these devices # send out bogus cell counter readings about once per week that # will mess up graphs if you don't cap them off. # # Cell counts are actually a nice fit here - with a full OC-3 pipe, # you can churn the counters in way less than 5 minutes, so this # gives headroom with a more reasonable sampling interval. oid pportInCells 1.3.6.1.4.1.277.1.4.2.1.43 oid pportOutCells 1.3.6.1.4.1.277.1.4.2.1.45 oid cktAtmInCells 1.3.6.1.4.1.277.1.6.1.1.88 oid cktAtmOutCells 1.3.6.1.4.1.277.1.6.1.1.89 # graph defaults for all ATM stats # always convert ATM cells to bits for graphing (53 * 8 = 424) graph --default-- scale = 424,* bytes = true y-axis = "bits per second" units = "bits/sec" cricket-1.0.5/sample-config/atm-interfaces/atm-rtr-uni/0040755000114300000000000000000010031605166021715 5ustar bertdwheelcricket-1.0.5/sample-config/atm-interfaces/atm-rtr-uni/Defaults0100644000114300000000000000144507365646654023437 0ustar bertdwheeldatasource AtmLPortInput ds-source = "snmp://%snmp%/pportInCells.%inst%" desc = "ATM Physical Port Data Rate - Input" rrd-ds-type = DERIVE rrd-heartbeat = 1800 rrd-min = 0 datasource AtmLPortOutput ds-source = "snmp://%snmp%/pportOutCells.%inst%" desc = "ATM Physical Port Data Rate - Output" rrd-ds-type = DERIVE rrd-heartbeat = 1800 rrd-min = 0 targetType atm-lport ds = "AtmLPortInput, AtmLPortOutput" graph AtmLPortInput draw-as = AREA color = dark-green legend = AtmPPortInput graph AtmLPortOutput draw-as = LINE1 color = dark-blue legend = AtmPPortOutput cricket-1.0.5/sample-config/atm-interfaces/atm-rtr-uni/interfaces0100644000114300000000000000122107365646654024003 0ustar bertdwheeltarget ATMSwitch1--9.1-EIP41INTB-INTB-CITY1CORE1-0.0.9.1 interface-desc = "9.1-EIP41INTB-INTB-CITY1CORE1-0.0" swname = "ATMSwitch1" rrd-max = 353208 InterfaceID = "9.1" target-type = atm-lport target ATMSwitch1--10.3-EIPXXINTB-INTB-CITY1CORE2-1.0.0.10.3 interface-desc = "10.3-EIPXXINTB-INTB-CITY1CORE2-1.0.0" swname = "ATMSwitch1" rrd-max = 353208 InterfaceID = "10.3" target-type = atm-lport target ATMSwitch2--10.1-EIPXXINTB-INTB-CITY2CORE2-0.0.10.1 interface-desc = "10.1-EIPXXINTB-INTB-CITY2CORE2-0.0" swname = "ATMSwitch2" rrd-max = 353208 InterfaceID = "10.1" target-type = atm-lport cricket-1.0.5/sample-config/atm-interfaces/atm-trunks/0040755000114300000000000000000010031605166021643 5ustar bertdwheelcricket-1.0.5/sample-config/atm-interfaces/atm-trunks/Defaults0100644000114300000000000000145507365646654023366 0ustar bertdwheeldatasource ATMPPortInput ds-source = "snmp://%snmp%/pportInCells.%inst%" desc = "ATM Physical Port Data Rate - Input" rrd-ds-type = DERIVE rrd-heartbeat = 1800 rrd-min = 0 datasource ATMPPortOutput ds-source = "snmp://%snmp%/pportOutCells.%inst%" desc = "ATM Physical Port Data Rate - Output" rrd-ds-type = DERIVE rrd-heartbeat = 1800 rrd-min = 0 targetType atm-trunk ds = "AtmPPortInput, AtmPPortOutput" graph ATMPPortInput draw-as = AREA color = dark-green legend = AtmPPortInput graph ATMPPortOutput draw-as = LINE1 color = dark-blue legend = AtmPPortOutput cricket-1.0.5/sample-config/atm-interfaces/atm-trunks/interfaces0100644000114300000000000000114607365646654023737 0ustar bertdwheeltarget ATMSwitch1--ATMSwitch1-ATMSwitch2-DS3-01.8.2 interface-desc = "ATMSwitch1-ATMSwitch2-DS3-01" swname = "ATMSwitch1" rrd-max = 96000 InterfaceID = "8.2" target-type = atm-trunk target ATMSwitch1--ATMSwitch1-ATMSwitch3-OC3-01.8.1 interface-desc = "ATMSwitch1-ATMSwitch3-DS3-01" swname = "ATMSwitch1" rrd-max = 353207 InterfaceID = "8.1" target-type = atm-trunk target ATMSwitch3--ATMSwitch1-ATMSwitch3-OC3-01.8.1 interface-desc = "ATMSwitch1-ATMSwitch3-DS3-01" swname = "ATMSwitch3" rrd-max = 353207 InterfaceID = "8.1" target-type = atm-trunk cricket-1.0.5/sample-config/Defaults0100644000114300000000000002130007775167133016341 0ustar bertdwheel# # Top Level Defaults # Target --default-- dataDir = %auto-base%/../cricket-data/%auto-target-path% email-program = /usr/bin/mailx rrd-datafile = %dataDir%/%auto-target-name%.rrd rrd-poll-interval = 300 persistent-alarms = false snmp-host = %auto-target-name% snmp-community = public snmp-port = 161 snmp-timeout = 2.0 snmp-retries = 5 snmp-backoff = 1.0 snmp-version = 1 snmp = %snmp-community%@%snmp-host%:%snmp-port%:%snmp-timeout%:%snmp-retries%:%snmp-backoff%:%snmp-version% summary-loc = top show-path = no OID sysUptime 1.3.6.1.2.1.1.3.0 OID ifNumber 1.3.6.1.2.1.2.1.0 OID ifIndex 1.3.6.1.2.1.2.2.1.1 OID ifDescr 1.3.6.1.2.1.2.2.1.2 OID ifType 1.3.6.1.2.1.2.2.1.3 OID ifMtu 1.3.6.1.2.1.2.2.1.4 OID ifSpeed 1.3.6.1.2.1.2.2.1.5 OID ifPhysAddress 1.3.6.1.2.1.2.2.1.6 OID ifAdminStatus 1.3.6.1.2.1.2.2.1.7 OID ifOperStatus 1.3.6.1.2.1.2.2.1.8 OID ifLastChange 1.3.6.1.2.1.2.2.1.9 OID ifInOctets 1.3.6.1.2.1.2.2.1.10 OID ifInUcastPkts 1.3.6.1.2.1.2.2.1.11 OID ifInNUcastPkts 1.3.6.1.2.1.2.2.1.12 OID ifInDiscards 1.3.6.1.2.1.2.2.1.13 OID ifInErrors 1.3.6.1.2.1.2.2.1.14 OID ifInUnknownProtos 1.3.6.1.2.1.2.2.1.15 OID ifOutOctets 1.3.6.1.2.1.2.2.1.16 OID ifOutUcastPkts 1.3.6.1.2.1.2.2.1.17 OID ifOutNUcastPkts 1.3.6.1.2.1.2.2.1.18 OID ifOutDiscards 1.3.6.1.2.1.2.2.1.19 OID ifOutErrors 1.3.6.1.2.1.2.2.1.20 OID ifOutQLen 1.3.6.1.2.1.2.2.1.21 OID ifHCInOctets 1.3.6.1.2.1.31.1.1.1.6 OID ifHCInUcastPkts 1.3.6.1.2.1.31.1.1.1.7 OID ifHCOutOctets 1.3.6.1.2.1.31.1.1.1.10 OID ifHCOutUcastPkts 1.3.6.1.2.1.31.1.1.1.11 OID ifAlias 1.3.6.1.2.1.31.1.1.1.18 dataSource --default-- rrd-ds-type = DERIVE # if no updates are received for 30 minutes, consider the datasource # unknown -- i.e. dead rrd-heartbeat = 1800 rrd-min = 0 dataSource ifInOctets ds-source = snmp://%snmp%/ifInOctets.%inst% dataSource ifOutOctets ds-source = snmp://%snmp%/ifOutOctets.%inst% # rra # # The RRA dictionary specifies the config of the datafiles on disk. # Unles you really know what you are doing, don't muck around with these. # Trust me, the defaults will do what you want. # this set of RRA's exactly duplicates the storage capacity of a # rateup logfile (this assumes rrd-poll-interval is 300 (5 min)) # The second field (0.5, below) is NEW as of RRD 0.99. It # is the xfiles-factor, which used to be in the target # dictionary. # one point per 5 minutes, spanning 50 hours rra 5minAve AVERAGE:0.5:1:600 # one point per 30 minutes, spanning 12 days rra 30minAve AVERAGE:0.5:6:600 # one point every 2 hours, spanning 50 days rra 2hrAve AVERAGE:0.5:24:600 rra 2hrMax MAX:0.5:24:600 # one point every day, spanning 600 days rra 1dayAve AVERAGE:0.5:288:600 rra 1dayMax MAX:0.5:288:600 # New aberrant behavior detection rra hwpredict HWPREDICT:1440:0.28:0.0024:288 # These shouldn't be defined if hwpredict is defined. #rra seasonal SEASONAL:288:0.1:2 #rra devpredict DEVPREDICT:1440:5 #rra devseasonal DEVSEASONAL:288:0.1:2 #rra failures FAILURES:288:7:9:5 # we will add datasources to each specific target-type later targetType --default-- rra = "5minAve, 30minAve, 2hrAve, 2hrMax, 1dayAve, 1dayMax" # rra = "5minAve, 30minAve, 2hrAve, 2hrMax, 1dayAve, 1dayMax, hwpredict" # The HTML dict has bits and pieces of HTML that gets used # by the grapher. html body-options bgcolor=white html page-header

%auto-title%


html contact-name the Cricket Admins html contact For questions or comments about this data, contact %contact-name%. html page-footer

Cricket
Version %auto-short-version%

%contact%

%auto-error%

html head %auto-title% # # Colors are in HTML #RRGGBB format, without the # # I'm not very creative. Cool color schemes would be a welcome addition. color dark-green 00cc00 color blue 0000ff color red ff0000 color cyan 00ffff color magenta ff00ff color yellow ffff00 color dark-red cc0000 color dark-blue 0000cc # thanks to Shannon Reis for these color cornflower 0080C0 color light-purple 8080C0 color pink FF0080 color purple 800080 color deep-blue 0000A0 color green 408080 color brown 808000 color black 000000 color neongrn 00FF00 color skyblue 0080FF color orange FF8000 color burntorng 800000 color purply FB31FB # this tells the grapher which colors to choose first, if # it was not given colors color --order-- "dark-green, blue, red, cyan, magenta, yellow, dark-red, dark-blue, cornflower, light-purple, pink, purple, deep-blue, green, brown, black, neongrn, skyblue, orange, burntorng purply" # graph # # This dictionary controls how data is presented. graph --default-- draw-as = LINE1 width = 500 height = 200 interleaved = false # Set these by looking at what size RRD makes your # finished graphs. It's too much trouble to try to calculate # them at runtime. If there is any doubt, don't set them. Bad hints # are worse than no hints! #width-hint = 595 #height-hint = 255 # if this attribute exists, then vertical rules will be placed # on the graphs at every zero-time, i.e. midnight, Monday, first of # the month, first of the year. vrule-color = red # Set use-gprint to print the min, max and curr values into the # graphic. #use-gprint = "true" graph ifInOctets color = dark-green draw-as = AREA legend = "Average bits in" y-axis = "bits per second" units = "bits/sec" scale = 8,* bytes = true graph ifOutOctets color = blue legend = "Average bits out" y-axis = "bits per second" units = "bits/sec" scale = 8,* bytes = true # map # # This dict is used to control the instance mapping code. # See the docs for more information about the map dictionary. map interface-name base-oid = ifDescr match = %interface-name% # This is a neat map that will let you tie Cricket target # names to the extended interface descriptions that Ciscos # support (called "aliases" by the MIB). For example, # if your target name is "corporate-ds3", then do # this on your Cisco: # # router#config term # router(config)#int Hssi/0/0/0 # router(config-if)#description [corporate-ds3] 45 Mbps DS3 to Corporate # router(config-if)#^Z # # And set inst to "map(alias-matches-target)" for that target. map alias-matches-target base-oid = ifAlias match = /^.*\\[%auto-target-name%\\].*$/ # events can be associated with a target, and then those # events will be marked on the graph. event --default-- color = magenta event test name = "Events were added to Cricket" date = "6/2/99 14:30" cricket-1.0.5/sample-config/http-performance/0040755000114300000000000000000010031605166020112 5ustar bertdwheelcricket-1.0.5/sample-config/http-performance/Defaults0100644000114300000000000000257707365646773021645 0ustar bertdwheeltarget --default-- short-desc = "" long-desc = "%short-desc% is being monitored via this URL: %url%." # you'll probably want to change this... unless you # have Cricket in ~/cricket and your config tree in ~/cricket-config util-dir = %auto-base%/../cricket/util target-type = http-performance url = "" datasource total-time ds-source = "exec:0:%util-dir%/test-url '%url%'" desc = "The amount of time it took to fetch the entire page. For large transfers, this will give an indication of data throughput." rrd-ds-type = GAUGE datasource first-time ds-source = "exec:1:%util-dir%/test-url '%url%'" desc = "The amount of time it took to fetch the first-byte. This time is the best approximation available of the time to connect (i.e. DNS time, TCP connection setup time, webserver response time)." rrd-ds-type = GAUGE targetType http-performance ds = "total-time, first-time" graph --default-- y-axis = "Time in Seconds" units = secs # cut off spikes above 5 seconds y-max = 5 graph total-time legend = "Total Time" draw-as = AREA graph first-time legend = "Time to First Byte" draw-as = LINE1 cricket-1.0.5/sample-config/http-performance/urls0100644000114300000000000000025107365646776021051 0ustar bertdwheeltarget cricket-home short-desc = "The Cricket Homepage" url = "http://www.munitions.com/~jra/test-file.txt" target www.cnn.com url = "http://www.cnn.com" cricket-1.0.5/sample-config/news-server/0040755000114300000000000000000010031605166017114 5ustar bertdwheelcricket-1.0.5/sample-config/news-server/Defaults0100644000114300000000000000322307365647141020622 0ustar bertdwheel# a subtree to monitor innd, by # Jost Krieger # this subtree is setup to fetch stats via ssh from # a remote news server. To do this from the background, # ssh authentication needs to be setup accordingly. # If you are running Cricket on the same host as INN, # then you can remove the references to remexec below. target --default-- # you'll probably want to change this... unless you # have Cricket in ~/cricket and your config tree in ~/config util-dir = %auto-base%/../cricket/util remexec = "/usr/local/net/bin/ssh -l %user% %server%" user = news server = nntp-host target-type = news-server datasource pass ds-source = "exec:0:%remexec% bin/ctlinnd mode | %util-dir%/newsstats" desc = "Number of articles that passed the filter." rrd-ds-type = DERIVE rrd-min = 0 datasource reject ds-source = "exec:1:%remexec% bin/ctlinnd mode | %util-dir%/newsstats" desc = "Number of articles rejected by the filter." rrd-ds-type = DERIVE rrd-min = 0 datasource refuse ds-source = "exec:2:%remexec% bin/ctlinnd mode | %util-dir%/newsstats" desc = "Number of articles refused." rrd-ds-type = DERIVE rrd-min = 0 targetType news-server ds = "pass, reject, refuse" graph --default-- y-axis = "# of articles" units = art. # cut off spikes above 5 seconds y-max = 30 graph pass legend = "Passed" draw-as = AREA graph reject legend = "Reject" draw-as = LINE1 graph refuse legend = "Refuse" draw-as = LINE1 cricket-1.0.5/sample-config/news-server/targets0100644000114300000000000000006107365647141020521 0ustar bertdwheeltarget news server = news user = news cricket-1.0.5/sample-config/perfmon/0040755000114300000000000000000010031605167016303 5ustar bertdwheelcricket-1.0.5/sample-config/perfmon/Defaults0100644000114300000000000004037307365647372020025 0ustar bertdwheel# Notes: # # y is the numerator - in this case the perfmon counter. # x is the denominator - usually a PerfTime counter. # tb is the timebase - usually PerfFreq. # datasource --default-- rrd-ds-type = FOR_SANITY_SET_ME rrd-max = 1288490188500 graph --default-- units = % legend = "If you're reading this, I need to add a legend!" DRAW-AS = LINE1 y-min = 0 # show-max = true si-units = false # show-avg-max = true #ds0 datasource PerfTime ds-source = "perfmon:%auto-target-name%::::perftime,nodur" rrd-ds-type = DERIVE #ds1 datasource PerfTime100ns ds-source = "perfmon:%auto-target-name%::::perftime100ns,nodur" rrd-ds-type = DERIVE #ds2 datasource PerfFreq ds-source = "perfmon:%auto-target-name%::::perffreq,nodur" rrd-ds-type = GAUGE # # System Counters # # #ds3 datasource rawbaseRegistryQuota ds-source = "perfmon:%auto-target-name%:System:% Registry Quota in Use::base" rrd-ds-type = GAUGE #ds4 datasource rawfractRegistryQuota ds-source = "perfmon:%auto-target-name%:System:% Registry Quota in Use::fraction" rrd-ds-type = GAUGE datasource RegistryQuota rrd-ds-type = COMPUTE rrd-cdef = ds4,ds3,/ graph RegistryQuota units = % legend = "% Registry Quota In Use" scale = 100,* draw-as = AREA y-max = 100 #ds5 datasource rawProcessorProcessorTime ds-source = "perfmon:%auto-target-name%:Processor:% Processor Time:_Total" rrd-ds-type = DERIVE datasource ProcessorTime rrd-ds-type = COMPUTE rrd-cdef = ds5,ds1,/ graph ProcessorTime legend = "% Processor Time" y-max = 100 draw-as = AREA color = dark-green units = % scale = -100,* #ds6 datasource rawProcessorInterruptTime ds-source = "perfmon:%auto-target-name%:Processor:% Interrupt Time:_Total" rrd-ds-type = DERIVE datasource InterruptTime rrd-ds-type = COMPUTE rrd-cdef = ds6,ds1,/ graph InterruptTime legend = "% Interrupt Time" units = % y-max = 100 scale = 100,* #ds7 datasource rawProcessorUserTime ds-source = "perfmon:%auto-target-name%:Processor:% User Time:_Total" rrd-ds-type = DERIVE datasource UserTime rrd-ds-type = COMPUTE rrd-cdef = ds7,ds1,/ graph UserTime legend = "% User Time" y-max = 100 units = % scale = 100,* #ds8 datasource rawProcessorPrivilegedTime ds-source = "perfmon:%auto-target-name%:Processor:% Privileged Time:_Total" rrd-ds-type = DERIVE datasource PrivilegedTime rrd-ds-type = COMPUTE rrd-cdef = ds8,ds1,/ graph PrivilegedTime legend = "% Privileged Time" units = % y-max = 100 scale = 100,* #ds9 datasource rawProcessorInterruptsSec ds-source = "perfmon:%auto-target-name%:Processor:Interrupts/sec:_Total" rrd-ds-type = DERIVE datasource InterruptsSec rrd-ds-type = COMPUTE rrd-cdef = ds9,ds0,ds2,/,/ graph InterruptsSec legend = "Interrupts/sec" units = "/sec" #ds10 datasource rawbaseMemoryCommitted ds-source = "perfmon:%auto-target-name%:Memory:% Committed Bytes In Use::base" rrd-ds-type = GAUGE #ds11 datasource rawfractMemoryCommitted ds-source = "perfmon:%auto-target-name%:Memory:% Committed Bytes In Use::fraction" rrd-ds-type = GAUGE datasource MemoryCommitted rrd-ds-type = COMPUTE rrd-cdef = ds11,ds10,/ graph MemoryCommitted legend = "% Committed Bytes" draw-as = AREA scale = 100,* y-max = 100 units = % #ds12 datasource rawbasePagingFileUsage ds-source = "perfmon:%auto-target-name%:Paging File:% Usage:_Total:base" rrd-ds-type = GAUGE #ds13 datasource rawfractPagingFileUsage ds-source = "perfmon:%auto-target-name%:Paging File:% Usage:_Total:fraction" rrd-ds-type = GAUGE datasource PagingFileUsage rrd-ds-type = COMPUTE rrd-cdef = ds13,ds12,/ graph PagingFileUsage legend = "% Paging File Usage (_Total)" units = % draw-as = LINE3 y-max = 100 scale = 100,* color = black #ds14 datasource rawbasePagingFilePeakUsage ds-source = "perfmon:%auto-target-name%:Paging File:% Usage Peak:_Total:base" rrd-ds-type = GAUGE #ds15 datasource rawfractPagingFilePeakUsage ds-source = "perfmon:%auto-target-name%:Paging File:% Usage Peak:_Total:fraction" rrd-ds-type = GAUGE datasource PagingFilePeakUsage rrd-ds-type = COMPUTE rrd-cdef = ds15,ds14,/ graph PagingFilePeakUsage legend = "% Paging File Peak Usage (_Total)" units = % y-max = 100 scale = 100,* draw-as = AREA color = dark-green #ds16 datasource rawPhysicalDiskDiskReads ds-source = "perfmon:%auto-target-name%:PhysicalDisk:Disk Reads/sec:_Total" rrd-ds-type = DERIVE datasource DiskReadsSec rrd-ds-type = COMPUTE rrd-cdef = ds16,ds0,ds2,/,/ graph DiskReadsSec legend = "Disk Reads/sec" units = "/sec" #ds17 datasource rawPhysicalDiskDiskWrites ds-source = "perfmon:%auto-target-name%:PhysicalDisk:Disk Writes/sec:_Total" rrd-ds-type = DERIVE datasource DiskWritesSec rrd-ds-type = COMPUTE rrd-cdef = ds17,ds0,ds2,/,/ graph DiskWritesSec legend = "Disk Writes/sec" units = "/sec" #ds18 datasource rawMemoryPagesSec ds-source = "perfmon:%auto-target-name%:Memory:Pages/sec" rrd-ds-type = DERIVE datasource MemoryPagesSec rrd-ds-type = COMPUTE rrd-cdef = ds18,ds0,ds2,/,/ graph MemoryPagesSec legend = "Memory Pages/sec" units = "/sec" #ds19 datasource ProcessorQueueLength ds-source = "perfmon:%auto-target-name%:System:Processor Queue Length:_Total" rrd-ds-type = GAUGE graph ProcessorQueueLength legend = "Processor Queue Length" draw-as = AREA color = red units = "seconds" #ds20 datasource ServerPoolPagedBytes ds-source = "perfmon:%auto-target-name%:Server:Pool Paged Bytes:_Total" rrd-ds-type = GAUGE graph ServerPoolPagedBytes legend = "Pool Paged Bytes" units = "bytes" precision = 0 draw-as = LINE3 color = black #ds21 datasource ServerPoolPagedPeak ds-source = "perfmon:%auto-target-name%:Server:Pool Paged Peak:_Total" rrd-ds-type = GAUGE graph ServerPoolPagedPeak legend "Pool Paged Peak" units = "bytes" draw-as = AREA color = dark-green precision = 0 #ds22 datasource Processes ds-source = "perfmon:%auto-target-name%:System:Processes" rrd-ds-type = GAUGE graph Processes legend = "Processes" units = "processes" precision = 0 #ds23 datasource Threads ds-source = "perfmon:%auto-target-name%:System:Threads" rrd-ds-type = GAUGE graph Threads legend = "Threads" units = "threads" precision = 0 #ds24 datasource TsActiveSessions ds-source = "perfmon:%auto-target-name%:Terminal Services:Active Sessions" rrd-ds-type = GAUGE graph TsActiveSessions units = "sessions" legend = "Active Sessions" draw-as = LINE3 precision = 0 #ds25 datasource TsInactiveSessions ds-source = "perfmon:%auto-target-name%:Terminal Services:Inactive Sessions" rrd-ds-type = GAUGE graph TsInactiveSessions units = "sessions" legend = "Inactive Sessions" draw-as = LINE3 precision = 0 datasource TsTotalSessions rrd-ds-type = COMPUTE rrd-cdef = ds24,ds25,+ graph TsTotalSessions units = "sessions" color = dark-green draw-as = AREA legend = "Sessions Total" precision = 0 # # DB Counters # #ds26 datasource rawSqlDbTransSecOnePoint ds-source = "perfmon:%auto-target-name%:SQLServer=Databases:Transactions/sec:OnePoint" rrd-ds-type = DERIVE datasource SqlDbTransSecOnePoint rrd-ds-type = COMPUTE rrd-cdef = ds26,ds0,ds2,/,/ graph SqlDbTransSecOnePoint legend = "Transactions/sec (OnePoint)" units = "/sec" draw-as = AREA #ds27 datasource rawbaseSqlLocksAvgWaitTime ds-source = "perfmon:%auto-target-name%:SQLServer=Locks:Average Wait Time (ms):database:base" rrd-ds-type = GAUGE #ds28 datasource rawfractSqlLocksAvgWaitTime ds-source = "perfmon:%auto-target-name%:SQLServer=Locks:Average Wait Time (ms):database:fraction" rrd-ds-type = GAUGE datasource LocksAvgWaitTime rrd-ds-type = COMPUTE rrd-cdef = ds28,ds27,/ graph LocksAvgWaitTime units = "ms" scale = 100,* legend = "Lock Average Wait Time (ms)" #ds29 datasource rawbaseSqlLockWaitTime ds-source = "perfmon:%auto-target-name%:SQLServer=Locks:Lock Wait Time (ms):database:base" rrd-ds-type = GAUGE #ds30 datasource rawfractSqlLockWaitTime ds-source = "perfmon:%auto-target-name%:SQLServer=Locks:Lock Wait Time (ms):database:fraction" rrd-ds-type = GAUGE datasource LocksWaitTime rrd-ds-type = COMPUTE rrd-cdef = ds30,ds29,/ graph LocksWaitTime units = "ms" scale = 100,* legend = "Lock Wait Time (ms)" #ds31 datasource rawSqlLocksLockReqsSec ds-source = "perfmon:%auto-target-name%:SQLServer=Locks:Lock Requests/sec:database" rrd-ds-type = DERIVE datasource LockReqsSec rrd-ds-type = COMPUTE rrd-cdef = ds31,ds0,ds2,/,/ graph LockReqsSec legend = "Lock Requests/sec (Database)" units = "/sec" #ds32 datasource rawSqlLocksLockTimeoutsSec ds-source = "perfmon:%auto-target-name%:SQLServer=Locks:Lock Timeouts/sec:database" rrd-ds-type = DERIVE datasource LockTimeoutsSec rrd-ds-type = COMPUTE rrd-cdef = ds32,ds0,ds2,/,/ graph LockTimeoutsSec legend = "Lock Timeouts/sec (Database)" units = "/sec" #ds33 datasource rawSqlLocksNumDeadlocksSec ds-source = "perfmon:%auto-target-name%:SQLServer=Locks:Number of Deadlocks/sec:database" rrd-ds-type = DERIVE datasource DeadlocksSec rrd-ds-type = COMPUTE rrd-cdef = ds33,ds0,ds2,/,/ graph DeadlocksSec legend = "Number of Deadlocks/sec (Database)" units = "/sec" #ds34 datasource SqlMemoryManagerTotalMemory ds-source = "perfmon:%auto-target-name%:SQLServer=Memory Manager:Total Server Memory (KB)" rrd-ds-type = GAUGE graph SqlMemoryManagerTotalMemory legend = "Total Server Memory (KB)" units = "MB" scale = 100,/ precision = 0 # IIS Counters #ds26 datasource rawIisBytesTotalSecDefault ds-source = "perfmon:%auto-target-name%:Web Service:Bytes Total/sec:Default Web Site" rrd-ds-type = DERIVE datasource IisBytesTotalSecDefault rrd-ds-type = COMPUTE rrd-cdef = ds26,ds0,ds2,/,/ graph IisBytesTotalSecDefault legend = "Bytes Total/sec (Default Web Site)" units = "bytes" #ds27 datasource rawIisAnonymousUsersSecDefault ds-source = "perfmon:%auto-target-name%:Web Service:Anonymous Users/sec:Default Web Site" rrd-ds-type = DERIVE datasource IisAnonymousUsersSecDefault rrd-ds-type = COMPUTE rrd-cdef = ds27,ds0,ds2,/,/ graph IisAnonymousUsersSecDefault legend = "Anonymous Users/sec (Default Web Site)" units = "/sec" precision = 0 #ds28 datasource rawIisFilesSecDefault ds-source = "perfmon:%auto-target-name%:Web Service:Files/sec:Default Web Site" rrd-ds-type = DERIVE datasource IisFilesSecDefault rrd-ds-type = COMPUTE rrd-cdef = ds28,ds0,ds2,/,/ graph IisFilesSecDefault legend = "Anonymous Users/sec (Default Web Site)" unis = "/sec" precision = 0 #ds29 datasource rawIisGetRequestsSecDefault ds-source = "perfmon:%auto-target-name%:Web Service:Get Requests/sec:Default Web Site" rrd-ds-type = DERIVE datasource IisGetRequestsSecDefault rrd-ds-type = COMPUTE rrd-cdef = ds29,ds0,ds2,/,/ graph IisGetRequestsSecDefault legend = "GET Requests/sec (Default Web Site)" units = "/sec" precision = 0 #ds30 datasource rawIisNonAnonymousUsersSecDefault rrd-ds-type = DERIVE ds-source = "perfmon:%auto-target-name%:Web Service:Non Anonymous Users/sec:Default Web Site" datasource IisNaUsersSecDefault rrd-ds-type = COMPUTE rrd-cdef = ds30,ds0,ds2,/,/ graph IisNaUsersSecDefault legend = "Non Anonymous Users/sec" units = "/sec" precision = 0 # # Target Definitions # # targetType DCAMMachine ds = "PerfTime,PerfTime100ns,PerfFreq,rawbaseRegistryQuota,rawfractRegistryQuota,rawProcessorProcessorTime,rawProcessorInterruptTime,rawProcessorUserTime,rawProcessorPrivilegedTime,rawProcessorInterruptsSec,rawbaseMemoryCommitted,rawfractMemoryCommitted,rawbasePagingFileUsage,rawfractPagingFileUsage,rawbasePagingFilePeakUsage,rawfractPagingFilePeakUsage,rawPhysicalDiskDiskReads,rawPhysicalDiskDiskWrites,rawMemoryPagesSec,ProcessorQueueLength,ServerPoolPagedBytes,ServerPoolPagedPeak,Processes,Threads,TsActiveSessions,TsInactiveSessions,rawSqlDbTransSecOnePoint,rawbaseSqlLocksAvgWaitTime,rawfractSqlLocksAvgWaitTime,rawbaseSqlLockWaitTime,rawfractSqlLockWaitTime,rawSqlLocksLockTimeoutsSec,rawSqlLocksNumDeadlocksSec,SqlMemoryManagerTotalMemory,DeadlocksSec,LockTimeoutsSec,LockReqsSec,LocksWaitTime,LocksAvgWaitTime,SqlDbTransSecOnePoint,RegistryQuota,ProcessorTime,InterruptTime,UserTime,PrivilegedTime,InterruptsSec,MemoryCommitted,PagingFileUsage,PagingFilePeakUsage,DiskReadsSec,DiskWritesSec,MemoryPagesSec,TsTotalSessions" view = "Registry: RegistryQuota, CPU: ProcessorTime InterruptTime PrivilegedTime, Memory Usage: MemoryCommitted , Paging File Usage: PagingFilePeakUsage PagingFileUsage, Disk: DiskReadsSec DiskWritesSec, Processor Queue: ProcessorQueueLength, Paging I/O: MemoryPagesSec, Processes: Processes Threads, Server Service Pool: ServerPoolPagedPeak ServerPoolPagedBytes, Terminal Services: TsTotalSessions TsActiveSessions TsInactiveSessions, Database Memory Usage: SqlMemoryManagerTotalMemory, Database Transactions (OnePoint): SqlDbTransSecOnePoint, Database Locks: LockReqsSec LockTimeoutsSec " targetType IISMachine ds = "PerfTime,PerfTime100ns,PerfFreq,rawbaseRegistryQuota,rawfractRegistryQuota,rawProcessorProcessorTime,rawProcessorInterruptTime,rawProcessorUserTime,rawProcessorPrivilegedTime,rawProcessorInterruptsSec,rawbaseMemoryCommitted,rawfractMemoryCommitted,rawbasePagingFileUsage,rawfractPagingFileUsage,rawbasePagingFilePeakUsage,rawfractPagingFilePeakUsage,rawPhysicalDiskDiskReads,rawPhysicalDiskDiskWrites,rawMemoryPagesSec,ProcessorQueueLength,ServerPoolPagedBytes,ServerPoolPagedPeak,Processes,Threads,TsActiveSessions,TsInactiveSessions, rawIisBytesTotalSecDefault,rawIisAnonymousUsersSecDefault,rawIisFilesSecDefault,rawIisGetRequestsSecDefault,rawIisNonAnonymousUsersSecDefault,IisBytesTotalSecDefault,IisAnonymousUsersSecDefault,IisFilesSecDefault,IisGetRequestsSecDefault,RegistryQuota,ProcessorTime,InterruptTime,UserTime,PrivilegedTime,InterruptsSec,MemoryCommitted,PagingFileUsage,PagingFilePeakUsage,DiskReadsSec,DiskWritesSec,IisNaUsersSecDefault,MemoryPagesSec,TsTotalSessions" view = "Registry: RegistryQuota, CPU: ProcessorTime InterruptTime PrivilegedTime, Memory Usage: MemoryCommitted, Paging File Usage: PagingFilePeakUsage PagingFileUsage, Disk: DiskReadsSec DiskWritesSec, Processor Queue: ProcessorQueueLength, Memory: InterruptsSec MemoryPagesSec, Processes: Processes Threads, Server Pool: ServerPoolPagedPeak ServerPoolPagedBytes, Terminal Services: TsTotalSessions TsActiveSessions TsInactiveSessions, IIS Traffic (Default): IisBytesTotalSecDefault, IIS Requests (Default): IisGetRequestsSecDefault IisFilesSecDefault, IIS Users (Default): IisAnonymousUsersSecDefault IisNonAnonymousUsersSecDefault" targetType --default-- ds = "PerfTime,PerfTime100ns,PerfFreq,rawbaseRegistryQuota,rawfractRegistryQuota,rawProcessorProcessorTime,rawProcessorInterruptTime,rawProcessorUserTime,rawProcessorPrivilegedTime,rawProcessorInterruptsSec,rawbaseMemoryCommitted,rawfractMemoryCommitted,rawbasePagingFileUsage,rawfractPagingFileUsage,rawbasePagingFilePeakUsage,rawfractPagingFilePeakUsage,rawPhysicalDiskDiskReads,rawPhysicalDiskDiskWrites,rawMemoryPagesSec,ProcessorQueueLength,ServerPoolPagedBytes,ServerPoolPagedPeak,Processes,Threads,TsActiveSessions,TsInactiveSessions,RegistryQuota,ProcessorTime,InterruptTime,UserTime,PrivilegedTime,InterruptsSec,MemoryCommitted,PagingFileUsage,PagingFilePeakUsage,DiskReadsSec,DiskWritesSec,MemoryPagesSec,TsTotalSessions" view = "Registry: RegistryQuota, CPU: ProcessorTime InterruptTime PrivilegedTime, Memory Usage: MemoryCommitted, Paging File Usage: PagingFilePeakUsage PagingFileUsage, Disk: DiskReadsSec DiskWritesSec, Processor Queue: ProcessorQueueLength, Paging I/O: MemoryPagesSec, Processes: Processes Threads, Server Service Pool: ServerPoolPagedPeak ServerPoolPagedBytes, Terminal Services: TsTotalSessions TsActiveSessions TsInactiveSessions " cricket-1.0.5/sample-config/perfmon/targets0100644000114300000000000000033007365647372017714 0ustar bertdwheeltarget mombox-1 target-type = DCAMMachine target mombox-2 target-type = DCAMMachine target iis-1-1 target-type = IISMachine target iis-1-2 target-type = IISMachine target generic-1 target generic-2 cricket-1.0.5/sample-config/portmasters/0040755000114300000000000000000010031605167017220 5ustar bertdwheelcricket-1.0.5/sample-config/portmasters/Defaults0100644000114300000000000000426507365647534020742 0ustar bertdwheel# Lucent Portmaster sub-tree # This was made via the Defaults of routers from original Cricket config. # If you manage to optimize or improve this, please return it to # canau@eunet.pt or to the Cricket developers Target --default-- portmaster = %auto-target-name% # fill in your domain name here domain = "" snmp-host = %portmaster%.%domain% pm3lineUsage = "%auto-base%/../cricket/util/pmlines.pl" pm3lines = "%pm3lineUsage% %snmp-community%@%snmp-host%" # you could set a router-specific community string here: # snmp-community = not-public # # Provide no default type, since each portmaster will be different, # and if we want to specify each model explicitly, or # get a warning. target-type = undef datasource --default-- rrd-ds-type = GAUGE rrd-heartbeat = 1800 rrd-min = undef rrd-max = undef datasource linesup ds-source = EXEC:0:%pm3lines% datasource modems ds-source = EXEC:1:%pm3lines% datasource isdn ds-source = EXEC:2:%pm3lines% ## targetType Portmaster2 ## ds = linesup ## view = "LinesUP: linesup" ## # this probably should be made some other way like getting all the ## # interfaces and subtrating one (scale = 1,-) but even then it wouldn't ## # count for the terminal connections like UUCP. ## # I no longer use PM2 for dialup access... good luck! targetType Portmaster3 ds = "linesup, modems, isdn" view = "LinesUP: linesup modems isdn" targetType Portmaster4 ds = "linesup, modems, isdn" view = "LinesUP: linesup modems isdn" # Portmaster4 should work like Portmaster3. In principle, %pm3lines% is # compatible with PM4. But you better check it. html short-desc Portmaster graph --default-- precision = integer graph linesup color = dark-green draw-as = AREA y-axis = "Lines UP" legend = "Lines UP" graph modems color = blue y-axis = "Lines used by modems" legend = "Lines used by modems" graph isdn color = red y-axis = "Lines used by ISDN" legend = "Lines used by ISDN" cricket-1.0.5/sample-config/portmasters/portmasters0100644000114300000000000000047407365647534021554 0ustar bertdwheeltarget dial0 target-type=Portmaster3 short-desc = "Portmaster3 0" target dial1 target-type=Portmaster3 short-desc = "Portmaster3 1" target dial2 target-type=Portmaster3 short-desc = "Portmaster3 0" target dial3 target-type=Portmaster3 short-desc = "Portmaster3 1" cricket-1.0.5/sample-config/router-interfaces/0040755000114300000000000000000010031605174020274 5ustar bertdwheelcricket-1.0.5/sample-config/router-interfaces/Defaults0100644000114300000000000000376507365647644022026 0ustar bertdwheel# router interface sub-tree # # This is where we collect stats on the interfaces of our routers # (i.e. Octets, Packets, Errors). These are kept seperate from the # routers themselves since they have quite different configuration needs. Target --default-- inst = map(interface-name) # snmp-community = secret snmp-host = %router% target-type = standard-interface # ifInOctets and ifOutOctets come from above, since they # are used by switch-ports as well datasource ifInErrors ds-source = snmp://%snmp%/ifInErrors.%inst% datasource ifOutErrors ds-source = snmp://%snmp%/ifOutErrors.%inst% datasource ifInUcastPackets ds-source = snmp://%snmp%/ifInUcastPkts.%inst% datasource ifOutUcastPackets ds-source = snmp://%snmp%/ifOutUcastPkts.%inst% targetType standard-interface ds = "ifInOctets, ifOutOctets, ifInErrors, ifOutErrors, ifInUcastPackets, ifOutUcastPackets" view = "Octets: ifInOctets ifOutOctets, UcastPackets: ifInUcastPackets ifOutUcastPackets, Errors: ifInErrors ifOutErrors" targetType sub-interface ds = "ifInOctets, ifOutOctets" view = "Octets: ifInOctets ifOutOctets" # likewise, graph params for ifInOctets and ifOutOctets come from above graph ifInUcastPackets color = dark-green draw-as = AREA y-axis = "packets per second" units = "pkt/sec" legend = "Average num Packets In" graph ifOutUcastPackets color = blue y-axis = "packets per second" units = "pkt/sec" legend = "Average num Packets Out" graph ifInErrors color = dark-green draw-as = AREA y-axis = "errors per second" units = "err/sec" legend = "Average Errors In" graph ifOutErrors color = blue y-axis = "errors per second" legend = "Average Errors Out" units = "err/sec" cricket-1.0.5/sample-config/router-interfaces/interfaces0100644000114300000000000000124307365647644022367 0ustar bertdwheeltarget --default-- router = bsn-router target Serial0_0_5 interface-name = Serial0/0/5 short-desc = "T1 to Nebraska" target Serial0_0_6 interface-name = Serial0/0/6 short-desc = "T-1 to Minnesota" target FastEthernet0_1_0 interface-name = FastEthernet0/1/0 short-desc = "Engineering Network" target FastEthernet1_0_0 interface-name = FastEthernet1/0/0 short-desc = "Engineering Servers Network" target FastEthernet1_1_0 interface-name = FastEthernet1/1/0 short-desc = "Router Nexus" target Hssi4_0_0 interface-name = Hssi4/0/0 short-desc = "DS3 (45 Mbit, full duplex) to Internet" cricket-1.0.5/sample-config/routers/0040755000114300000000000000000010031605174016336 5ustar bertdwheelcricket-1.0.5/sample-config/routers/Defaults0100644000114300000000000000756707365647747020100 0ustar bertdwheel# router sub-tree # # This is where we collect stats on the chassis of our routers # (i.e. memory, CPU, temperature). These are kept separate from # the interfaces themselves since they have quite different # configuration needs. Target --default-- router = %auto-target-name% snmp-host = %router% # you could set a router-specific community string here: # snmp-community = not-public # # Provide no default type, since each router will be different, # and if we want to specify each model explicitly, or # get a warning. target-type = undef # OID's we'll be using in this tree OID cpu1min 1.3.6.1.4.1.9.2.1.57.0 OID cpu5min 1.3.6.1.4.1.9.2.1.58.0 OID tempInlet 1.3.6.1.4.1.9.9.13.1.3.1.3.1 OID tempOutlet 1.3.6.1.4.1.9.9.13.1.3.1.3.3 OID tempState 1.3.6.1.4.1.9.9.13.1.3.1.6.1 OID mem5minUsed 1.3.6.1.4.1.9.9.48.1.1.1.5.1 OID mem5minFree 1.3.6.1.4.1.9.9.48.1.1.1.6.1 datasource --default-- rrd-ds-type = GAUGE rrd-heartbeat = 1800 rrd-min = undef rrd-max = undef datasource cpu1min ds-source = snmp://%snmp%/cpu1min datasource cpu5min ds-source = snmp://%snmp%/cpu5min datasource tempInlet ds-source = snmp://%snmp%/tempInlet datasource tempOutlet ds-source = snmp://%snmp%/tempOutlet datasource tempState ds-source = snmp://%snmp%/tempState datasource mem5minUsed ds-source = snmp://%snmp%/mem5minUsed datasource mem5minFree ds-source = snmp://%snmp%/mem5minFree targetType Cisco-7200-Router ds = "cpu1min, cpu5min, tempInlet, tempOutlet, mem5minUsed, mem5minFree" view = "cpu: cpu1min cpu5min, temperature: tempInlet tempOutlet, memory: mem5minUsed mem5minFree" targetType Cisco-7500-Router ds = "cpu1min, cpu5min, tempInlet, tempOutlet, mem5minUsed, mem5minFree" view = "cpu: cpu1min cpu5min, temperature: tempInlet tempOutlet, memory: mem5minUsed mem5minFree" # 3600 routers do not report temperature -- they only # report tempterature states: # normal(1), # warning(2), # critical(3), # shutdown(4), # notPresent(5) targetType Cisco-3600-Router ds = "cpu1min, cpu5min, tempState, mem5minUsed, mem5minFree" view = "cpu: cpu1min cpu5min, tempAlarm: tempState, memory: mem5minUsed mem5minFree" # 2500 routers don't even report the temperature. Feh. targetType Cisco-2500-Router ds = "cpu1min, cpu5min, mem5minUsed, mem5minFree" view = "cpu: cpu1min cpu5min, memory: mem5minUsed mem5minFree" html short-desc Router Chassis graph cpu1min units = "%" color = dark-green draw-as = AREA y-axis = "Percent CPU utilization" legend = "1 minute CPU utilization" # fixed y-axis, since this is a percentage y-min = 0 y-max = 100 graph cpu5min units = "%" color = blue y-axis = "Percent CPU utilization" legend = "5 minute CPU utilization" # fixed y-axis, since this is a percentage y-min = 0 y-max = 100 graph tempInlet color = dark-green draw-as = AREA y-axis = "degrees Centigrade" units = "°" space = "" legend = "Inlet temperature in C" precision = integer graph tempOutlet color = blue y-axis = "degrees Centigrade" units = "°" space = "" legend = "Outlet temperature in C" precision = integer graph mem5minUsed color = dark-green draw-as = AREA y-axis = "Bytes" legend = "5 minute used memory" graph mem5minFree color = blue y-axis = "Bytes" legend = "5 minute free memory" cricket-1.0.5/sample-config/routers/Targets0100644000114300000000000000031107365647747017716 0ustar bertdwheeltarget engineering-router target-type=Cisco-7500-Router short-desc = "Router for engineering folks" target main-router target-type=Cisco-7500-Router short-desc = "Main router" cricket-1.0.5/sample-config/routing/0040755000114300000000000000000010031605174016322 5ustar bertdwheelcricket-1.0.5/sample-config/routing/Defaults0100644000114300000000000000520007365650114020020 0ustar bertdwheel# routing subtree: by Jeff Jensen # # Lets you track BGP route announcements to help you correlate # periods of route instability with unexpected traffic shifts. target --default-- snmp-host = %auto-target-name%.%domain% rrd-datafile = %dataDir%/%auto-target-name%-%inst%.rrd # # You'll probably need to change the snmp community # snmp-community = public target-type = bgp-speaker OID bgpPeerInUpdates 1.3.6.1.2.1.15.3.1.10 OID bgpPeerOutUpdates 1.3.6.1.2.1.15.3.1.11 OID bgpPeerInTotalMessages 1.3.6.1.2.1.15.3.1.12 OID bgpPeerOutTotalMessages 1.3.6.1.2.1.15.3.1.13 # # Define the BGP datasources # datasource bgpPeerInUpdates ds-source = snmp://%snmp%/bgpPeerInUpdates.%inst% desc = "The number of BGP UPDATE messages received on this connection." datasource bgpPeerOutUpdates ds-source = snmp://%snmp%/bgpPeerOutUpdates.%inst% desc = "The number of BGP UPDATE messages transmitted on this connection." datasource bgpPeerInTotalMessages ds-source = snmp://%snmp%/bgpPeerInTotalMessages.%inst% desc = "The total number of messages received from the remote peer on this connection." datasource bgpPeerOutTotalMessages ds-source = snmp://%snmp%/bgpPeerOutTotalMessages.%inst% desc = "The total number of messages transmitted on this connection." # # I've found different views useful at different times # targetType bgp-speaker ds = "bgpPeerInUpdates, bgpPeerOutUpdates, bgpPeerInTotalMessages, bgpPeerOutTotalMessages" view = "Updates: bgpPeerInUpdates bgpPeerOutUpdates, Total Messages: bgpPeerInTotalMessages bgpPeerOutTotalMessages, Total In: bgpPeerInUpdates bgpPeerInTotalMessages, Total Out: bgpPeerOutUpdates bgpPeerOutTotalMessages" # # Setup the graph dictionaries for these datasources # # I set the defaults for these two graph dictionary entries # graph --default-- y-axis = "Updates" precision = 2 units = "updates/s" graph bgpPeerInUpdates draw-as = AREA legend = "Inbound Updates" graph bgpPeerOutUpdates legend = "Outbound Updates" # # Change the defaults for these two # graph --default-- y-axis = "Messages" precision = 2 units = "messages/s" graph bgpPeerInTotalMessages draw-as = AREA legend = "Inbound Messages" graph bgpPeerOutTotalMessages legend = "Outbound Messages" cricket-1.0.5/sample-config/routing/Targets0100644000114300000000000000134207365650114017665 0ustar bertdwheel# # The SNMP host is defined as target name.domain, in this case it comes out # to internet-router-1.foo.com. You'll need to make sure that host is SNMP # reachable. # # The inst values are the IP addresses of your BGP peers. In the Cisco world # these are from the neighbor statements in the BGP router section. # target internet-router-1 domain = "foo.com" short-desc = "Primary Internet Router" inst = "('172.16.1.1','172.18.1.1')" inst-names = "('Peer 16','Peer 18')" # # You can also look at both of these on the same HTML page # target all-peers domain = "" short-desc = "All BGP Peers" targets = "internet-router-1:'172.16.1.1'; internet-router-1:'172.18.1.1'" cricket-1.0.5/sample-config/squid-proxy/0040755000114300000000000000000010031605174017137 5ustar bertdwheelcricket-1.0.5/sample-config/squid-proxy/Defaults0100644000114300000000000002057507365650233020653 0ustar bertdwheel# squid-proxy sub-tree # # A contributed config from Ragnar Kjrstad # Minor modifications by Jost Krieger # target --default-- target-type = squid-proxy snmp-host = %auto-target-name% # you could set a router-specific community string here: snmp-community = public OID ClientHttpRequests 1.3.6.1.4.1.3495.1.3.2.1.1 OID HttpHits 1.3.6.1.4.1.3495.1.3.2.1.2 OID HttpErrors 1.3.6.1.4.1.3495.1.3.2.1.3 OID HttpInKb 1.3.6.1.4.1.3495.1.3.2.1.4 OID HttpOutKb 1.3.6.1.4.1.3495.1.3.2.1.5 OID ICPpktsSent 1.3.6.1.4.1.3495.1.3.2.1.6 OID ICPpktsRecv 1.3.6.1.4.1.3495.1.3.2.1.7 OID ICPkbSent 1.3.6.1.4.1.3495.1.3.2.1.8 OID ICPkbRecv 1.3.6.1.4.1.3495.1.3.2.1.9 OID ServerRequests 1.3.6.1.4.1.3495.1.3.2.1.10 OID ServerErrors 1.3.6.1.4.1.3495.1.3.2.1.11 OID ServerInKb 1.3.6.1.4.1.3495.1.3.2.1.12 OID ServerOutKb 1.3.6.1.4.1.3495.1.3.2.1.13 OID CurrentSwapSize 1.3.6.1.4.1.3495.1.3.2.1.14 OID SysPageFaults 1.3.6.1.4.1.3495.1.3.1.1 OID MemUsage 1.3.6.1.4.1.3495.1.3.1.4 OID CpuUsage 1.3.6.1.4.1.3495.1.3.1.5 OID MaxResSize 1.3.6.1.4.1.3495.1.3.1.6 OID NumObjCount 1.3.6.1.4.1.3495.1.3.1.7 OID CurrentLRUExpiration 1.3.6.1.4.1.3495.1.3.1.8 OID CurrentUnlinkRequests 1.3.6.1.4.1.3495.1.3.1.9 OID CurrentUnusedFileDescrCount 1.3.6.1.4.1.3495.1.3.1.10 OID CurrentReservedFileDescrCount 1.3.6.1.4.1.3495.1.3.1.11 OID HttpAllSvcTimeFiveMin 1.3.6.1.4.1.3495.1.3.2.2.1.2.5 OID HttpMissSvcTimeFiveMin 1.3.6.1.4.1.3495.1.3.2.2.1.3.5 OID HttpNmSvcTimeFiveMin 1.3.6.1.4.1.3495.1.3.2.2.1.4.5 OID HttpHitSvcTimeFiveMin 1.3.6.1.4.1.3495.1.3.2.2.1.5.5 OID IcpQuerySvcTimeFiveMin 1.3.6.1.4.1.3495.1.3.2.2.1.6.5 OID IcpReplySvcTimeFiveMin 1.3.6.1.4.1.3495.1.3.2.2.1.7.5 OID DnsSvcTimeFiveMin 1.3.6.1.4.1.3495.1.3.2.2.1.8.5 OID HttpAllSvcTimeHourly 1.3.6.1.4.1.3495.1.3.2.2.1.2.60 OID HttpMissSvcTimeHourly 1.3.6.1.4.1.3495.1.3.2.2.1.3.60 OID HttpNmSvcTimeHourly 1.3.6.1.4.1.3495.1.3.2.2.1.4.60 OID HttpHitSvcTimeHourly 1.3.6.1.4.1.3495.1.3.2.2.1.5.60 OID IcpQuerySvcTimeHourly 1.3.6.1.4.1.3495.1.3.2.2.1.6.60 OID IcpReplySvcTimeHourly 1.3.6.1.4.1.3495.1.3.2.2.1.7.60 OID DnsSvcTimeHourly 1.3.6.1.4.1.3495.1.3.2.2.1.8.60 datasource --default-- rrd-ds-type = DERIVE rrd-heartbeat = 1800 rrd-min = 0 rrd-max = undef datasource ClientHttpRequests ds-source = snmp://%snmp%/ClientHttpRequests datasource HttpHits ds-source = snmp://%snmp%/HttpHits datasource HttpErrors ds-source = snmp://%snmp%/HttpErrors datasource HttpInKb ds-source = snmp://%snmp%/HttpInKb datasource HttpOutKb ds-source = snmp://%snmp%/HttpOutKb datasource ICPpktsSent ds-source = snmp://%snmp%/ICPpktsSent datasource ICPpktsRecv ds-source = snmp://%snmp%/ICPpktsRecv datasource ICPkbSent ds-source = snmp://%snmp%/ICPkbSent datasource ICPkbRecv ds-source = snmp://%snmp%/ICPkbRecv datasource ServerRequests ds-source = snmp://%snmp%/ServerRequests datasource ServerErrors ds-source = snmp://%snmp%/ServerErrors datasource ServerInKb ds-source = snmp://%snmp%/ServerInKb datasource ServerOutKb ds-source = snmp://%snmp%/ServerOutKb datasource CurrentSwapSize ds-source = snmp://%snmp%/CurrentSwapSize rrd-ds-type = GAUGE datasource SysPageFaults ds-source = snmp://%snmp%/SysPageFaults datasource MemUsage ds-source = snmp://%snmp%/MemUsage rrd-ds-type = GAUGE datasource CpuUsage ds-source = snmp://%snmp%/CpuUsage datasource MaxResSize ds-source = snmp://%snmp%/MaxResSize rrd-ds-type = GAUGE datasource NumObjCount ds-source = snmp://%snmp%/NumObjCount rrd-ds-type = GAUGE datasource CurrentLRUExpiration ds-source = snmp://%snmp%/CurrentLRUExpiration rrd-ds-type = GAUGE datasource CurrentUnlinkRequests ds-source = snmp://%snmp%/CurrentUnlinkRequests rrd-ds-type = GAUGE datasource CurrentUnusedFileDescrCount ds-source = snmp://%snmp%/CurrentUnusedFileDescrCount rrd-ds-type = GAUGE datasource CurrentReservedFileDescrCount ds-source = snmp://%snmp%/CurrentReservedFileDescrCount rrd-ds-type = GAUGE datasource HttpAllSvcTimeFiveMin ds-source = snmp://%snmp%/HttpAllSvcTimeFiveMin rrd-ds-type = GAUGE datasource HttpMissSvcTimeFiveMin ds-source = snmp://%snmp%/HttpMissSvcTimeFiveMin rrd-ds-type = GAUGE datasource HttpNmSvcTimeFiveMin ds-source = snmp://%snmp%/HttpNmSvcTimeFiveMin rrd-ds-type = GAUGE datasource HttpHitSvcTimeFiveMin ds-source = snmp://%snmp%/HttpHitSvcTimeFiveMin rrd-ds-type = GAUGE datasource IcpQuerySvcTimeFiveMin ds-source = snmp://%snmp%/IcpQuerySvcTimeFiveMin rrd-ds-type = GAUGE datasource IcpReplySvcTimeFiveMin ds-source = snmp://%snmp%/IcpReplySvcTimeFiveMin rrd-ds-type = GAUGE datasource DnsSvcTimeFiveMin ds-source = snmp://%snmp%/DnsSvcTimeFiveMin rrd-ds-type = GAUGE datasource HttpAllSvcTimeHourly ds-source = snmp://%snmp%/HttpAllSvcTimeHourly rrd-ds-type = GAUGE datasource HttpMissSvcTimeHourly ds-source = snmp://%snmp%/HttpMissSvcTimeHourly rrd-ds-type = GAUGE datasource HttpNmSvcTimeHourly ds-source = snmp://%snmp%/HttpNmSvcTimeHourly rrd-ds-type = GAUGE datasource HttpHitSvcTimeHourly ds-source = snmp://%snmp%/HttpHitSvcTimeHourly rrd-ds-type = GAUGE datasource IcpQuerySvcTimeHourly ds-source = snmp://%snmp%/IcpQuerySvcTimeHourly rrd-ds-type = GAUGE datasource IcpReplySvcTimeHourly ds-source = snmp://%snmp%/IcpReplySvcTimeHourly rrd-ds-type = GAUGE datasource DnsSvcTimeHourly ds-source = snmp://%snmp%/DnsSvcTimeHourly rrd-ds-type = GAUGE graph CurrentLRUExpiration rrd-graph-args = --logarithmic color = dark-green si-units = false scale = 100,/,3600,/,24,/ legend = "Current LRU expiration (logarithmic scale)" y-axis "days" targetType squid-proxy ds = "ClientHttpRequests, HttpHits, HttpErrors, HttpInKb, HttpOutKb, ICPpktsSent, ICPpktsRecv, ICPkbSent, ICPkbRecv, ServerRequests, ServerErrors, ServerInKb, ServerOutKb, CurrentSwapSize, SysPageFaults, MemUsage, CpuUsage, MaxResSize, NumObjCount, CurrentUnlinkRequests, CurrentUnusedFileDescrCount, CurrentReservedFileDescrCount, HttpAllSvcTimeFiveMin, HttpMissSvcTimeFiveMin, HttpNmSvcTimeFiveMin, HttpHitSvcTimeFiveMin, IcpQuerySvcTimeFiveMin, IcpReplySvcTimeFiveMin, DnsSvcTimeFiveMin, HttpAllSvcTimeHourly, HttpMissSvcTimeHourly, HttpNmSvcTimeHourly, HttpHitSvcTimeHourly, IcpQuerySvcTimeHourly, IcpReplySvcTimeHourly, DnsSvcTimeHourly, CurrentLRUExpiration" view = "Traffic: ServerInKb ServerOutKb, ICPTraffic: ICPkbSent ICPkbRecv, ClientRequests: ServerRequests ServerErrors, SwapSize: CurrentSwapSize, ICPPackets: ICPpktsSent ICPpktsRecv, ClientHttpRequests: ClientHttpRequests HttpHits HttpErrors, HttpTraffic: HttpInKb HttpOutKb, SysPageFaults: SysPageFaults, MemUsage: MemUsage, CpuUsage: CpuUsage, FileDescr: CurrentUnusedFileDescrCount CurrentReservedFileDescrCount, SvcFiveMin: HttpAllSvcTimeFiveMin HttpMissSvcTimeFiveMin HttpNmSvcTimeFiveMin HttpHitSvcTimeFiveMin DnsSvcTimeFiveMin, IcpSvcFiveMin: IcpQuerySvcTimeFiveMin IcpReplySvcTimeFiveMin, SvcHourly: HttpAllSvcTimeHourly HttpMissSvcTimeHourly HttpNmSvcTimeHourly HttpHitSvcTimeHourly DnsSvcTimeHourly, IcpSvcHourly: IcpQuerySvcTimeHourly IcpReplySvcTimeHourly, NumObjCount: NumObjCount, CurrentLRUExpiration: CurrentLRUExpiration" cricket-1.0.5/sample-config/subtree-times/0040755000114300000000000000000010031605174017423 5ustar bertdwheelcricket-1.0.5/sample-config/subtree-times/Defaults0100644000114300000000000000411707365650347021137 0ustar bertdwheel# Subtree for monitoring collection times for collect-subtree runs. The # syntax for the targets file is: 'target '. # # Originally written by Ethan Toretta . Currently # being maintained by Adam Meltzer . # # To utilize this tree, uncomment the line '# $subtreeTimes = 1' in the # collect-subtrees script - this is turned off by default since it leaves # extra cruft which is unnecessary if you don't use this. # Target --default-- target-type = "subtree-times" directory-desc = "Just how long is the collector taking?" disable-short-desc = true long-desc = "%auto-target-name% collector information" bindir = %auto-base%/../cricket/util datasource --default-- rrd-ds-type = GAUGE rrd-heartbeat = 1800 rrd-min = 0 rrd-max = 4294967295 graph --default-- draw-as = LINE1 targetType subtree-times ds = "secondsPerRun, targetsPerRun, targetsPerSecond" view = "# of targets: targetsPerRun, Subtree times: secondsPerRun, Throughput: targetsPerSecond" datasource secondsPerRun ds-source = "exec:0:%bindir%/get-collector-stats %auto-target-name%" desc = "Seconds elapsed per run" graph secondsPerRun y-axis = "Seconds" units = "s" legend = "Seconds per run" datasource targetsPerRun ds-source = "exec:1:%bindir%/get-collector-stats %auto-target-name%" desc = "The number of targets collected" graph targetsPerRun y-axis = "Targets" units = "targets" legend = "targets per run" # the line tends to be hard to see, so: draw-as = LINE2 datasource targetsPerSecond ds-source = "exec:2:%bindir%/get-collector-stats %auto-target-name%" desc = "Targets processed per second" graph targetsPerSecond y-axis = "targets/sec" units = "t/s" legend = "Targets per second" cricket-1.0.5/sample-config/subtree-times/targets0100644000114300000000000000050007363620164021021 0ustar bertdwheeltarget normal # before uncommenting these targets, these sets need to defined in # cricket/subtree-sets #target atm-interfaces #target http-performance #target news-server #target portmasters #target router-interfaces #target routers #target routing #target squid-proxy #target switch-ports #target switches #target usr cricket-1.0.5/sample-config/switch-ports/0040755000114300000000000000000010031605174017301 5ustar bertdwheelcricket-1.0.5/sample-config/switch-ports/Defaults0100644000114300000000000000064207365650450021007 0ustar bertdwheel# switch-ports sub-tree # # This is where we collect stats on switch ports. We inherit pretty # much everything we need, since it's in the root Default file. Target --default-- rrd-datafile = %dataDir%/%auto-target-name%-%inst%.rrd switch = %auto-target-name% snmp-host = %switch% target-type = switch-port targetType switch-port ds = "ifInOctets, ifOutOctets" cricket-1.0.5/sample-config/switch-ports/switches0100644000114300000000000000122607365650450021070 0ustar bertdwheel# inst can take the form of any Perl expression that evaluates to # a list: (1, 2, 3, 4) (1..10, 12..35) (5, 3, 7, 11, 9, 2) # We will be fetching SNMP from these switches via the unqualified # hostname (to see why, search for snmp-host in the Defaults file in # this directory). If you need to add a domain, see # sample-config/routers for an example. target --default-- # cisco 1900's have 25 ports, plus an A and B port inst=(1..27) target cisco-1900-1 target cisco-1900-2 target cisco-1900-3 target cisco-1900-4 target cisco-1900-5 # here's an isolated instance of a beefy switch with lots of ports target cisco-5500-1 inst=(1..70) cricket-1.0.5/sample-config/switches/0040755000114300000000000000000010031605174016464 5ustar bertdwheelcricket-1.0.5/sample-config/switches/Defaults0100644000114300000000000001025507365650613020174 0ustar bertdwheel# switches sub-tree # # Contributed by Jeff Jensen . # Hacked by Jeff Allen... any mistakes are mine. Target --default-- switch = %auto-target-name% snmp-host = %switch% # you can set this, if you need to: # snmp-community = private # we leave this unset here so that we MUST remember to set it later target-type = undef OID c1900Bandwidth 1.3.6.1.4.1.437.1.1.3.7.1.0 OID c1900BandwidthMax 1.3.6.1.4.1.437.1.1.3.7.5.0 OID c5000Bandwidth 1.3.6.1.4.1.9.5.1.1.8.0 OID c5000BandwidthMax 1.3.6.1.4.1.9.5.1.1.19.0 OID cpu1min 1.3.6.1.4.1.9.2.1.57.0 OID cpu5min 1.3.6.1.4.1.9.2.1.58.0 OID mem5minUsed 1.3.6.1.4.1.9.9.48.1.1.1.5.1 OID mem5minFree 1.3.6.1.4.1.9.9.48.1.1.1.6.1 datasource --default-- rrd-ds-type = GAUGE rrd-heartbeat = 1800 rrd-min = undef rrd-max = undef datasource c1900-bandwidth ds-source = snmp://%snmp%/c1900Bandwidth desc = "The amount of bandwidth used across the backplane." datasource c1900-bandwidth-max ds-source = snmp://%snmp%/c1900BandwidthMax desc = "The maximum amount of bandwidth used across the backplane." datasource c5000-bandwidth ds-source = snmp://%snmp%/c5000Bandwidth desc = "The amount of bandwidth used across the backplane." datasource c5000-bandwidth-max ds-source = snmp://%snmp%/c5000BandwidthMax rrd-heartbeat = 1800 desc = "The maximum amount of bandwidth used across the backplane". datasource cpu1min ds-source = snmp://%snmp%/cpu1min rrd-ds-type = GAUGE rrd-heartbeat = 1800 desc = "The average usage of the CPU over the last 1 minute." datasource cpu5min ds-source = snmp://%snmp%/cpu5min rrd-ds-type = GAUGE rrd-heartbeat = 1800 desc = "The average usage of the CPU over the last 5 minutes." datasource mem5minUsed ds-source = snmp://%snmp%/mem5minUsed rrd-ds-type = GAUGE rrd-heartbeat = 1800 desc = "The amount of memory used over the last 5 minutes." datasource mem5minFree ds-source = snmp://%snmp%/mem5minFree rrd-ds-type = GAUGE rrd-heartbeat = 1800 desc = "The amount of memory free over the last 5 minutes." graph c1900-bandwidth units = bs color = dark-green draw-as = AREA y-axis = "Bits" legend = "Bits/s across backplane" graph c1900-bandwidth-max units = bs color = blue y-axis = "Bits" legend = "Max bits/s across backplane during interval" graph c5000-bandwidth units = bs color = dark-green draw-as = AREA y-axis = "Percent backplane utilization" legend = "Percent backplane utilization" graph c5000-bandwidth-max units = bs color = blue y-axis = "Max percentage backplane utilization" legend = "Max percentage backplane utilization" graph cpu1min units = % color = dark-green draw-as = AREA y-axis = "Percent CPU utilization" legend = "1 minute CPU utilization" precision = integer graph cpu5min units = % color = blue y-axis = "Percent CPU utilization" legend = "5 minute CPU utilization" precision = integer graph mem5minUsed units = B draw-as = AREA y-axis = "Bytes" legend = "5 minute used memory" precision = integer graph mem5minFree units = B y-axis = "Bytes" legend = "5 minute free memory" precision = integer targetType cisco-2900xl ds = "cpu1min, cpu5min, mem5minUsed, mem5minFree" view = "cpu: cpu1min cpu5min, memory: mem5minUsed mem5minFree" targetType cisco-1900 ds = "c1900-bandwidth, c1900-bandwidth-max" view = "backplane: c1900-bandwidth c1900-bandwidth-max" targetType cisco-2926 ds = "c5000-bandwidth, c5000-bandwidth-max" view = "backplane: c5000-bandwidth c5000-bandwidth-max" cricket-1.0.5/sample-config/switches/switches0100644000114300000000000000047507365650613020261 0ustar bertdwheel# Here is an example of a set of switches. They are grouped by # switch type to minimize the number of "target-type" tags we need. target --default-- target-type = cisco-1900 target c1900-1 target c1900-2 target c1900-3 target --default-- target-type = cisco-2926 target c2629-1 target engineering-switch cricket-1.0.5/sample-config/systemperf/0040755000114300000000000000000010031605174017034 5ustar bertdwheelcricket-1.0.5/sample-config/systemperf/Defaults0100644000114300000000000002361510000635644020533 0ustar bertdwheel##################################################################### # Top-level defaults file for host performance monitoring with the # Net-SNMP daemon (http://www.net-snmp.org/) # # Integrated into Cricket 1.0.4 on 28-mar-2002 by Bert Driehuis, # with many changes. # # Based on this work: # Started 12-16-99 by James Moore # # Change Log : # 28/01/2000 : UCD-SNMP OID's added # : default datasource added # : default graph added # - Stuart Grimshaw # 02-01-2000 : Changed OIDs for UCD load averages # : Added graphs for ucd disk usage # - James Moore # # To Do : # ##################################################################### target --default-- server = "" snmp-host = %server% display-name = "%auto-target-name% on %server%" min-size = 0 max-size = undef OID hrSystemNumUsers 1.3.6.1.2.1.25.1.5.0 OID hrSystemProcesses 1.3.6.1.2.1.25.1.6 OID hrStorageName 1.3.6.1.2.1.25.2.3.1.3 OID hrStorageSize 1.3.6.1.2.1.25.2.3.1.5 OID hrStorageUsed 1.3.6.1.2.1.25.2.3.1.6 # Load averages. OID ucd_load1min 1.3.6.1.4.1.2021.10.1.3.1 OID ucd_load5min 1.3.6.1.4.1.2021.10.1.3.2 OID ucd_load15min 1.3.6.1.4.1.2021.10.1.3.3 # Memory stats OID ucd_memswapAvail 1.3.6.1.4.1.2021.4.4.0 OID ucd_memrealAvail 1.3.6.1.4.1.2021.4.6.0 OID ucd_memtotalAvail 1.3.6.1.4.1.2021.4.11.0 # Disk stats (Don't forget the instance number...) OID ucd_diskfree 1.3.6.1.4.1.2021.9.1.7 OID ucd_diskused 1.3.6.1.4.1.2021.9.1.8 OID ucd_diskpused 1.3.6.1.4.1.2021.9.1.9 # CPU Stats OID ucd_cpuUser 1.3.6.1.4.1.2021.11.9.0 OID ucd_cpuSystem 1.3.6.1.4.1.2021.11.10.0 OID ucd_cpuIdle 1.3.6.1.4.1.2021.11.11.0 OID ucd_rawCpuUser 1.3.6.1.4.1.2021.11.50.0 OID ucd_rawCpuNice 1.3.6.1.4.1.2021.11.51.0 OID ucd_rawCpuSystem 1.3.6.1.4.1.2021.11.52.0 OID ucd_rawCpuIdle 1.3.6.1.4.1.2021.11.53.0 # Disk I/O OID ucd_diskIODevice 1.3.6.1.4.1.2021.13.15.1.1.2 #OID ucd_diskIONRead 1.3.6.1.4.1.2021.13.15.1.1.3 #OID ucd_diskIONWrite 1.3.6.1.4.1.2021.13.15.1.1.4 OID ucd_diskIOReads 1.3.6.1.4.1.2021.13.15.1.1.5 OID ucd_diskIOWrites 1.3.6.1.4.1.2021.13.15.1.1.6 ##### Datasources ######### datasource hrSystemNumUsers ds-source = snmp://%snmp%/hrSystemNumUsers rrd-ds-type = GAUGE datasource hrSystemProcesses ds-source = snmp://%snmp%/hrSystemProcesses.%inst% rrd-ds-type = GAUGE datasource hrStorageSize ds-source = snmp://%snmp%/hrStorageSize.%inst% rrd-ds-type = GAUGE datasource hrStorageUsed ds-source = snmp://%snmp%/hrStorageUsed.%inst% rrd-ds-type = GAUGE map hr-storage-name base-oid = hrStorageName match = %hr-storage-name% datasource ucd_load1min ds-source = snmp://%snmp%/ucd_load1min rrd-ds-type = GAUGE datasource ucd_load5min ds-source = snmp://%snmp%/ucd_load5min rrd-ds-type = GAUGE datasource ucd_load15min ds-source = snmp://%snmp%/ucd_load15min rrd-ds-type = GAUGE datasource ucd_memswapAvail ds-source = snmp://%snmp%/ucd_memswapAvail rrd-ds-type = GAUGE datasource ucd_memrealAvail ds-source = snmp://%snmp%/ucd_memrealAvail rrd-ds-type = GAUGE datasource ucd_memtotalAvail ds-source = snmp://%snmp%/ucd_memtotalAvail rrd-ds-type = GAUGE datasource ucd_diskfree ds-source = snmp://%snmp%/ucd_diskfree.%inst% rrd-ds-type = GAUGE datasource ucd_diskused ds-source = snmp://%snmp%/ucd_diskused.%inst% rrd-ds-type = GAUGE datasource ucd_diskpused ds-source = snmp://%snmp%/ucd_diskpused.%inst% rrd-ds-type = GAUGE datasource ucd_cpuUser ds-source = snmp://%snmp%/ucd_cpuUser rrd-ds-type = GAUGE datasource ucd_cpuSystem ds-source = snmp://%snmp%/ucd_cpuSystem rrd-ds-type = GAUGE datasource ucd_cpuIdle ds-source = snmp://%snmp%/ucd_cpuIdle rrd-ds-type = GAUGE datasource ucd_rawCpuUser ds-source = snmp://%snmp%/ucd_rawCpuUser rrd-ds-type = DERIVE datasource ucd_rawCpuNice ds-source = snmp://%snmp%/ucd_rawCpuNice rrd-ds-type = DERIVE datasource ucd_rawCpuSystem ds-source = snmp://%snmp%/ucd_rawCpuSystem rrd-ds-type = DERIVE datasource ucd_rawCpuIdle ds-source = snmp://%snmp%/ucd_rawCpuIdle rrd-ds-type = DERIVE datasource ucd_diskIONRead ds-source = snmp://%snmp%/ucd_diskIONRead.%inst% rrd-ds-type = DERIVE datasource ucd_diskIONWrite ds-source = snmp://%snmp%/ucd_diskIONWrite.%inst% rrd-ds-type = DERIVE datasource ucd_diskIOReads ds-source = snmp://%snmp%/ucd_diskIOReads.%inst% rrd-ds-type = DERIVE datasource ucd_diskIOWrites ds-source = snmp://%snmp%/ucd_diskIOWrites.%inst% rrd-ds-type = DERIVE datasource ifInErrors ds-source = snmp://%snmp%/ifInErrors.%inst% datasource ifOutErrors ds-source = snmp://%snmp%/ifOutErrors.%inst% datasource ifInUcastPackets ds-source = snmp://%snmp%/ifInUcastPkts.%inst% datasource ifOutUcastPackets ds-source = snmp://%snmp%/ifOutUcastPkts.%inst% map ucd-diskio-device base-oid = ucd_diskIODevice match = %ucd-diskio-device% #### Target Types ######### targetType hr_System ds = "hrSystemProcesses, hrSystemNumUsers" view = "Processes: hrSystemProcesses, Users: hrSystemNumUsers" targetType hr_Storage ds = "hrStorageUsed, hrStorageSize" view = "Storage: hrStorageUsed" targetType ucd_System ds = "ucd_rawCpuUser, ucd_rawCpuNice, ucd_rawCpuSystem, ucd_rawCpuIdle, ucd_memrealAvail, ucd_memswapAvail, ucd_memtotalAvail, ucd_load1min, ucd_load5min, ucd_load15min" view = "cpu: ucd_rawCpuUser ucd_rawCpuNice ucd_rawCpuSystem, Memory: ucd_memrealAvail ucd_memswapAvail ucd_memtotalAvail, Load: ucd_load1min ucd_load5min ucd_load15min" targetType ucd_Storage ds = "ucd_diskfree, ucd_diskused, ucd_diskpused" view = "Storage: ucd_diskfree ucd_diskused, Percent Used: ucd_diskpused" targetType ucd_diskIO ds = "ucd_diskIOReads, ucd_diskIOWrites" view = "diskIO: ucd_diskIOReads ucd_diskIOWrites" targetType ucd_diskIO_bsdi ds = "ucd_diskIOReads" view = "diskIO: ucd_diskIOReads" targetType standard-interface ds = "ifInOctets, ifOutOctets, ifInErrors, ifOutErrors, ifInUcastPackets, ifOutUcastPackets" view = "Octets: ifInOctets ifOutOctets, UcastPackets: ifInUcastPackets ifOutUcastPackets, Errors: ifInErrors ifOutErrors" #### Graphs ############### graph --default-- y-max = undef width-hint = undef height-hint = undef graph ucd_memswapAvail legend = "Available swap space" scale = 1024,* bytes = true units = "bytes" rrd-graph-args = --logarithmic graph ucd_memrealAvail legend = "Used RAM" scale = 1024,* bytes = true units = "bytes" graph ucd_memtotalAvail legend = "Available RAM" scale = 1024,* bytes = true units = "bytes" graph ucd_load1min legend = "1 Min Load Av" si-units= false graph ucd_load5min legend = "5 Min Load Av" si-units= false graph ucd_load15min legend = "15 Min Load Av" si-units= false graph hrSystemNumUsers color = dark-green draw-as = AREA y-axis = "Users" units = "Users" legend = "# of Users" precision = integer graph hrSystemProcesses color = dark-green draw-as = AREA y-axis = "Processes" units = "Processes" legend = "# of system Processes" precision = integer graph hrStorageUsed draw-as = LINE1 y-axis = "Bytes used" units = "Bytes" legend = "Bytes of space used" bytes = true scale = %units% #y-min = %min-size% y-min = 1048576 y-max = %max-size% height-hint = undef width-hint = undef rrd-graph-args = --logarithmic graph ucd_diskfree color = dark-green draw-as = AREA y-axis = "Bytes" legend = "Bytes of space free" bytes = true y-max = %max-size% y-min = 0 scale = 1024,* graph ucd_diskused color = red draw-as = AREA y-axis = "Bytes" legend = "Bytes of space used" bytes = true y-max = %max-size% y-min = 0 scale = 1024,* graph ucd_diskpused color = blue draw-as = AREA y-axis = "%" units = "%" legend = "Percent of space used" graph ucd_cpuUser draw-as = AREA y-min = 0 y-max = 100 graph ucd_cpuSystem draw-as = STACK y-min = 0 y-max = 100 graph ucd_cpuIdle draw-as = STACK y-min = 0 y-max = 100 graph ucd_rawCpuUser draw-as = AREA y-min = 0 y-max = 100 graph ucd_rawCpuNice draw-as = STACK y-min = 0 y-max = 100 graph ucd_rawCpuSystem draw-as = STACK y-min = 0 y-max = 100 graph ucd_rawCpuIdle draw-as = STACK y-min = 0 y-max = 100 graph ifInUcastPackets draw-as = AREA y-axis = "packets per second" units = "pkt/sec" legend = "Average num Packets In" graph ifOutUcastPackets y-axis = "packets per second" units = "pkt/sec" legend = "Average num Packets Out" graph ifInErrors draw-as = AREA y-axis = "errors per second" units = "err/sec" legend = "Average Errors In" graph ifOutErrors y-axis = "errors per second" legend = "Average Errors Out" units = "err/sec" cricket-1.0.5/sample-config/usr/0040755000114300000000000000000010031605174015444 5ustar bertdwheelcricket-1.0.5/sample-config/usr/Defaults0100644000114300000000000000233407365650746017162 0ustar bertdwheel# a subtree for monitoring USR modem chassis, contributed # by Jeremy Fischer , based on a modem # monitoring script by Jeff Jensen target --default-- modem = %auto-target-name% short-desc = "" # You'll need to set your domain domain = yournetwork.net # you'll probably want to change this... unless you # have Cricket in ~/cricket and your config tree in ~/config util-dir = %auto-base%/../cricket/util #snmp-community = private snmp-host = "%modem%.%domain%" usrGetUsage = "%auto-base%/../cricket/util/usrModemUsage" usrGet = "%usrGetUsage% %modem% %snmp-community%" target-type = USRModem datasource --default-- rrd-ds-type = GAUGE rrd-heartbeat = 1800 rrd-min = undef rrd-max = undef datasource linesup ds-source = EXEC:0:%usrGet% targetType USRModem ds = "linesup" view = "LinesUP: linesup" html short-desc USRModems graph --default-- precision = integer graph linesup color = dark-green draw-as = AREA y-axis = "Lines Used" legend = "Lines Used" cricket-1.0.5/sample-config/usr/targets0100644000114300000000000000050307365650746017060 0ustar bertdwheeltarget usr1 target-type=USRModem short-desc = "Bank 1 of the modems" target usr2 target-type=USRModem short-desc = "Bank 2 of the modems" target usr3 target-type=USRModem short-desc = "Bank 3 of the modems" target usr4 target-type=USRModem short-desc = "Bank 4 of the modems" cricket-1.0.5/sample-config/wbem/0040755000114300000000000000000010031605174015565 5ustar bertdwheelcricket-1.0.5/sample-config/wbem/Defaults0100644000114300000000000000121307770173524017270 0ustar bertdwheelTarget --default-- wbem-ns = root\\cimv2 wbem-host = %auto-target-name% wbem-obj = Win32_PerfRawData_Special_Application_Data directory-desc = "Monitoring the health of the DBS" datasource --default-- rrd-ds-type = COUNTER graph --default-- units = "/sec" datasource FileDownloadStream ds-source = "wbem:%wbem-host%:%wbem-ns%:%wbem-obj%:FileDownloadStream" datasource NotificationsStream ds-source = "wbem:%wbem-host%:%wbem-ns%:%wbem-obj%:NotificationsStream" datasource TotalBytes ds-source = "wbem:%wbem-host%:%wbem-ns%:%wbem-obj%:TotalBytes" targetType --default-- ds = "FileDownloadStream, NotificationsStream, TotalBytes" cricket-1.0.5/sample-config/wbem/targets0100644000114300000000000000005407770173524017174 0ustar bertdwheeltarget wbem-host-1111 target wbem-host-1112 cricket-1.0.5/util/0040755000114300000000000000000010031605174013064 5ustar bertdwheelcricket-1.0.5/util/getFormat.c0100644000114300000000000002025707510452776015203 0ustar bertdwheel/* * getFormat * * Program that computes the rrd_format structures for Cricket Arch. * * To compile: * gcc -I -o getFormat getFormat.c * * I make an assumptions that the unival unions arrays are linear and there * is NO padding other than the normal padding for unions and that the double * value of the union is bigger than the unsigned long... * * Contributed by Ed Bugg . Bug fixes by Melissa * D. Binde and Jeff Allen . * */ #include #include #include #include #include #include #include void format_stat_head (void) { stat_head_t h1; size_t addr1, addr2; int pad; /* Padding if needed */ int total=0; char rrdFormat[80]; memset(rrdFormat, 0, sizeof(rrdFormat)); addr1 = (size_t)&h1.cookie; addr2 = (size_t)&h1.version; if ((addr2 - addr1) == sizeof(h1.cookie)) { sprintf(rrdFormat, "a4"); } else { pad = addr2 - addr1 - sizeof(h1.cookie); sprintf(rrdFormat, "a4 x%d", pad); total += pad; } total += sizeof(h1.cookie); addr1 = addr2; addr2 = (size_t)&h1.float_cookie; if ((addr2 - addr1) == sizeof(h1.version)) { sprintf(rrdFormat, "%s a5", rrdFormat); } else { pad = addr2 - addr1 - sizeof(h1.version); sprintf(rrdFormat, "%s a5 x%d", rrdFormat, pad); total += pad; } total += sizeof(h1.version); addr1 = addr2; addr2 = (size_t)&h1.ds_cnt; if ((addr2 - addr1) == sizeof(h1.float_cookie)) { sprintf(rrdFormat, "%s d", rrdFormat); } else { pad = addr2 - addr1 - sizeof(h1.float_cookie); sprintf(rrdFormat, "%s d x%d", rrdFormat, pad); total += pad; } total += sizeof(h1.float_cookie); addr1 = addr2; addr2 = (size_t)&h1.rra_cnt; if ((addr2 - addr1) == sizeof(h1.ds_cnt)) { sprintf(rrdFormat, "%s %c", rrdFormat, (sizeof(h1.ds_cnt)==8)?'Q':'L'); } else { pad = addr2 - addr1 - sizeof(h1.ds_cnt); sprintf(rrdFormat, "%s %c x%d", rrdFormat, (sizeof(h1.ds_cnt)==8)?'Q':'L', pad); total += pad; } total += sizeof(h1.ds_cnt); addr1 = addr2; addr2 = (size_t)&h1.pdp_step; if ((addr2 - addr1) == sizeof(h1.rra_cnt)) { sprintf(rrdFormat, "%s %c", rrdFormat, (sizeof(h1.rra_cnt)==8)?'Q':'L'); } else { pad = addr2 - addr1 - sizeof(h1.rra_cnt); sprintf(rrdFormat, "%s %c x%d", rrdFormat, (sizeof(h1.rra_cnt)==8)?'Q':'L', pad); total += pad; } total += sizeof(h1.rra_cnt); addr1 = addr2; addr2 = (size_t)&h1.par; if ((addr2 - addr1) == sizeof(h1.pdp_step)) { sprintf(rrdFormat, "%s %c", rrdFormat, (sizeof(h1.pdp_step)==8)?'Q':'L'); } else { pad = addr2 - addr1 - sizeof(h1.pdp_step); sprintf(rrdFormat, "%s %c x%d", rrdFormat, (sizeof(h1.pdp_step)==8)?'Q':'L', pad); total += pad; } total += sizeof(h1.pdp_step); pad = sizeof(h1) - total; sprintf(rrdFormat, "%s x%d", rrdFormat, pad); printf("$self->{'statHead'} = \"%s\"\n", rrdFormat); } void format_ds_def (void) { ds_def_t h2; int total=0; char rrdFormat[80]; size_t addr1, addr2; int pad; memset(rrdFormat, 0, sizeof(rrdFormat)); addr1 = (size_t)&h2.ds_nam; addr2 = (size_t)&h2.dst; if ((addr2 - addr1) == sizeof(h2.ds_nam)) { sprintf(rrdFormat, "a%d", DS_NAM_SIZE); } else { pad = addr2 - addr1 - sizeof(h2.ds_nam); sprintf(rrdFormat, "a%d x%d", DS_NAM_SIZE, pad); total += pad; } total += sizeof(h2.ds_nam); addr1 = addr2; addr2 = (size_t)&h2.par; if ((addr2 - addr1) == sizeof(h2.dst)) { sprintf(rrdFormat, "%s a%d", rrdFormat, DST_SIZE); } else { pad = addr2 - addr1 - sizeof(h2.dst); sprintf(rrdFormat, "%s a%d x%d", rrdFormat, DST_SIZE, pad); total += pad; } total += sizeof(h2.dst); /* Heartbeat is a long vs min and max values are doubles */ if (sizeof(unival) - sizeof(unsigned long)) { sprintf(rrdFormat, "%s %c x%d", rrdFormat, (sizeof(unival)==8)?'Q':'L', sizeof(unival) - sizeof(unsigned long)); } else { sprintf(rrdFormat, "%s %c", rrdFormat, (sizeof(unival)==8)?'Q':'L'); } total += sizeof(unival); sprintf(rrdFormat, "%s d d", rrdFormat); total += (2* sizeof(unival)); pad = sizeof(h2) - total; if (pad) { sprintf(rrdFormat, "%s x%d", rrdFormat, pad); } printf("$self->{'dsDef'} = \"%s\"\n", rrdFormat); } void format_rra_def (void) { rra_def_t h2; int total=0; char rrdFormat[80]; size_t addr1, addr2; int pad; memset(rrdFormat, 0, sizeof(rrdFormat)); addr1 = (size_t)&h2.cf_nam; addr2 = (size_t)&h2.row_cnt; if ((addr2 - addr1) == sizeof(h2.cf_nam)) { sprintf(rrdFormat, "a%d", CF_NAM_SIZE); } else { pad = addr2 - addr1 - sizeof(h2.cf_nam); sprintf(rrdFormat, "a%d x%d", CF_NAM_SIZE, pad); total += pad; } total += sizeof(h2.cf_nam); addr1 = addr2; addr2 = (size_t)&h2.pdp_cnt; if ((addr2 - addr1) == sizeof(h2.row_cnt)) { sprintf(rrdFormat, "%s %c", rrdFormat, (sizeof(h2.row_cnt)==8)?'Q':'L'); } else { pad = addr2 - addr1 - sizeof(h2.row_cnt); sprintf(rrdFormat, "%s %c x%d", rrdFormat, (sizeof(h2.row_cnt)==8)?'Q':'L', pad); total += pad; } total += sizeof(h2.row_cnt); addr1 = addr2; addr2 = (size_t)&h2.par; if ((addr2 - addr1) == sizeof(h2.pdp_cnt)) { sprintf(rrdFormat, "%s %c", rrdFormat, (sizeof(h2.pdp_cnt)==8)?'Q':'L'); } else { pad = addr2 - addr1 - sizeof(h2.pdp_cnt); sprintf(rrdFormat, "%s %c x%d", rrdFormat, (sizeof(h2.pdp_cnt)==8)?'Q':'L', pad); total += pad; } total += sizeof(h2.pdp_cnt); /* xff_val is a double */ sprintf(rrdFormat, "%s d", rrdFormat); total += sizeof(unival); pad = sizeof(h2) - total; if (pad) { sprintf(rrdFormat, "%s x%d", rrdFormat, pad); } printf("$self->{'rraDef'} = \"%s\"\n", rrdFormat); } void format_live_head (void) { char rrdFormat[80]; memset(rrdFormat, 0, sizeof(rrdFormat)); sprintf(rrdFormat, "%c", (sizeof(time_t)==8)?'Q':'L'); printf("$self->{'liveHead'} = \"%s\"\n", rrdFormat); } void format_pdp_prep (void) { pdp_prep_t h2; int total=0; char rrdFormat[80]; size_t addr1, addr2; int pad; memset(rrdFormat, 0, sizeof(rrdFormat)); addr1 = (size_t)&h2.last_ds; addr2 = (size_t)&h2.scratch; if((addr2 - addr1) == sizeof(h2.last_ds)) { sprintf(rrdFormat, "a%d", LAST_DS_LEN); } else { pad = addr2 - addr1 - sizeof(h2.last_ds); sprintf(rrdFormat, "a%d x%d", LAST_DS_LEN, pad); total += pad; } total += sizeof(h2.last_ds); /* unknown sec is a long and pdp_val is a double */ if (sizeof(unival) - sizeof(unsigned long)) { sprintf(rrdFormat, "%s %c x%d", rrdFormat, (sizeof(unival)==8)?'Q':'L', sizeof(unival) - sizeof(unsigned long)); } else { sprintf(rrdFormat, "%s %c", rrdFormat, (sizeof(unival)==8)?'Q':'L'); } total += sizeof(unival); sprintf(rrdFormat, "%s d", rrdFormat); total += sizeof(unival); pad = sizeof(h2) - total; if (pad) { sprintf(rrdFormat, "%s x%d", rrdFormat, pad); } printf("$self->{'pdpDef'} = \"%s\"\n", rrdFormat); } void format_cdp_prep (void) { cdp_prep_t h2; int total=0; char rrdFormat[80]; int pad; memset(rrdFormat, 0, sizeof(rrdFormat)); sprintf(rrdFormat, "d"); /* cdp_val is a double */ total += sizeof(unival); if (sizeof(unival) - sizeof(unsigned long)) { sprintf(rrdFormat, "%s %c x%d", rrdFormat, (sizeof(unival)==8)?'Q':'L', sizeof(unival) - sizeof(unsigned long)); } else { sprintf(rrdFormat, "%s %c", rrdFormat, (sizeof(unival)==8)?'Q':'L'); } total += sizeof(unival); pad = sizeof(h2) - total; if (pad) { sprintf(rrdFormat, "%s x%d", rrdFormat, pad); } printf("$self->{'cdpDef'} = \"%s\"\n", rrdFormat); } void format_rra_ptr (void) { char rrdFormat[80]; memset(rrdFormat, 0, sizeof(rrdFormat)); sprintf(rrdFormat, "%c", (sizeof(&rrdFormat)==8)?'Q':'L'); printf("$self->{'rraPtr'} = \"%s\"\n", rrdFormat); } void format_element (void) { char rrdFormat[80]; sprintf(rrdFormat, "d"); /* Assuming all data elements are just doubles * This is pretty safe */ printf("$self->{'element'} = \"%s\"\n", rrdFormat); } int main (void) { format_stat_head(); format_ds_def(); format_rra_def(); format_pdp_prep(); format_cdp_prep(); format_live_head(); format_rra_ptr(); format_element(); return 0; } cricket-1.0.5/util/README0100644000114300000000000000706707560023435013762 0ustar bertdwheelThis directory has contributed goodies and other useful, but not critical programs in it. Each is documented, sparingly, below. leanODBC: A Perl extension that wraps some ODBC 3.x Calls. For Win32 platform only. usrModemUsage: This tool can be run from Cricket via an EXEC datasource to collect stats on the number of modems in use in a USR Total Control chassis. Give it hostname and community string on the commandline, and it will give you number of modems in use and number of modems in negotiation on lines 0 and 1 of it's output. pmlines.pl: A tool to tell you the total number of modem and ISDN sessions active on a Portmaster. dump-targets: Lets you see what collector will be working on before you let the collector loose on your config tree. Useful for testing out brand new configs. rrd-dump: A simple program to exercise RRD::File, but it could also be used to debug problems, etc. If RRD::File is lying to you, so will rrd-dump! Be warned! test-url: A script to fetch a URL and tell you how long it took. This can be used via an EXEC datasource to monitor HTTP and FTP server response times. listInterfaces: Give this script a router name and a community string and it will make a set of target definitions for you. Very useful! rrd-tune: Use this to force an rrd "tune" operation on parts of your config tree. You'll need to use this to make changes to rrd-min, rrd-max, or rrd-heartbeat stick if you make them after the RRD files already exist. rrdTuneAberrant.cgi: A CGI script meant to be invoked from a web browser that invokes the rrdtool tune aberrant-reset option for the specified target and ds (datasource): rrdTuneAberrant.cgi?target=%2switch-1%2Fport_2&ds=ifOutOctets The argument target should be the complete config tree path. An inst argument can be used to specify the instance number when appropriate. Using this script may require the set user-id bit to be enabled: chmod 4755 rrdTuneAberrant.cgi relocate-perl: Used during install to fix the location of Perl in the #! lines of the scripts. getFormat.c: If you need to port RRD::Format to your architecture, you can use this little C program to help out. See the comments in ../lib/RRD/Format.pm. newsstats: This script is called by the news-server subtree to format stats from INN's ctlinnd command. generate-statics: This is a nifty little tool to take snapshots of the GIFs for a bunch of targets and deposit them into a directory to be served as parts of static HTML documents. This can be used to make Cricket snappier, or to archive interesting GIFs in an automated way. This script depends on some tags in order to know where to put things. Put these tags in your root Defaults file in the target dictionary: static-ranges -> ranges to calculate for (such as d:y) static-path -> file system path to place the image static-name -> base file name for the image For example: static-ranges = d:w:m static-path = %auto-base%/../public_html/static/%auto-target-path% static-name = %auto-target-name% You either need to edit the script to set $gBaseURL correctly, or you need to use -baseURL to set it. (Or, if you are feeling lucky, you can ignore it and hope I guessed the right URL for your machine. Don't laugh, it might work...) Now, put a tag named "generate-static" in any target which you want to be generated. Finally, run generate-statics from cron. Making HTML pages that incorporate the generated image files is left as an exercise to the reader. cricket-1.0.5/util/LeanODBC/0040755000114300000000000000000010031605175014374 5ustar bertdwheelcricket-1.0.5/util/LeanODBC/LeanODBC.pm0100755000114300000000000001373207770453344016266 0ustar bertdwheelpackage LeanODBC; use strict; use Carp; use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $AUTOLOAD); require Exporter; require DynaLoader; require AutoLoader; @ISA = qw(Exporter DynaLoader); @EXPORT_OK = qw( SQL_ERROR SQL_HANDLE_DBC SQL_HANDLE_DESC SQL_HANDLE_ENV SQL_HANDLE_STMT SQL_INVALID_HANDLE SQL_NO_DATA SQL_NO_DATA_FOUND SQL_NTS SQL_NULL_DATA SQL_NULL_HANDLE SQL_NULL_HDBC SQL_NULL_HDESC SQL_NULL_HENV SQL_NULL_HSTMT SQL_SUCCESS SQL_SUCCESS_WITH_INFO SQLAllocHandle SQLCloseCursor SQLConnect SQLDisconnect SQLDriverConnect SQLExecDirect SQLFetch SQLFreeHandle SQLGetData SQLGetDiagRec SQLNumResultCols ); %EXPORT_TAGS = (ALL => [ qw( SQL_ERROR SQL_HANDLE_DBC SQL_HANDLE_DESC SQL_HANDLE_ENV SQL_HANDLE_STMT SQL_INVALID_HANDLE SQL_NO_DATA SQL_NO_DATA_FOUND SQL_NTS SQL_NULL_DATA SQL_NULL_HANDLE SQL_NULL_HDBC SQL_NULL_HDESC SQL_NULL_HENV SQL_NULL_HSTMT SQL_SUCCESS SQL_SUCCESS_WITH_INFO SQLAllocHandle SQLCloseCursor SQLConnect SQLDisconnect SQLDriverConnect SQLExecDirect SQLFetch SQLFreeHandle SQLGetData SQLGetDiagRec SQLNumResultCols )]); $VERSION = '0.90'; sub AUTOLOAD { # This AUTOLOAD is used to 'autoload' constants from the constant() # XS function. If a constant is not found then control is passed # to the AUTOLOAD in AutoLoader. my $constname; ($constname = $AUTOLOAD) =~ s/.*:://; croak "& not defined" if $constname eq 'constant'; my $val = constant($constname, @_ ? $_[0] : 0); if ($! != 0) { if ($! =~ /Invalid/) { $AutoLoader::AUTOLOAD = $AUTOLOAD; goto &AutoLoader::AUTOLOAD; } else { croak "Your vendor has not defined LeanODBC macro $constname"; } } no strict 'refs'; *$AUTOLOAD = sub { $val }; goto &$AUTOLOAD; } bootstrap LeanODBC $VERSION; # Preloaded methods go here. # Autoload methods go after =cut, and are processed by the autosplit program. 1; __END__ # Below is the stub of documentation for your module. You better edit it! =head1 NAME LeanODBC - This extension is a thin wrapper to a restricted subset of the ODBC 3.x API. =head1 SYNOPSIS use LeanODBC (:ALL); @ret = SQLAllocHandle(SQL_HANDLE_HENV,SQL_NULL_HANDLE); $henv = $ret[1]; @ret = SQLAllocHandle(SQL_HANDLE_DBC,$henv); $hdbc = $ret[1]; @ret = SQLConnect($hdbc,'Sample DSN','MyUser','MyPwd'); if ($ret[0] != SQL_SUCCESS) { @ret = SQLGetDiagRec(SQL_HANDLE_DBC,$hdbc,1); print join(' ',@ret) . "\n"; } else { ... } If this looks like raw ODBC API calls, that's intentional. =head1 DESCRIPTION This extension is a thin wrapper to a restricted subset of the ODBC 3.x API. It is not object-oriented and the functions share the same names as their ODBC 3.x counterparts. The restricted subset of the API only includes Core Interface functions for read-only operations. The LeanODBC package exports no symbols by default. You can either import a specific list of symbols or use the tag 'ALL' to import everything. To connect to an existing data source, LeanODBC exposes SQLConnect. Alternatively, you may specific a complete connect string and use SQLDriverConnect. =head1 Constants The following constants are exposed by the wrapper. Refer to the ODBC 3.x API documentation for their interpretation. SQL_ERROR SQL_HANDLE_DBC SQL_HANDLE_DESC SQL_HANDLE_ENV SQL_HANDLE_STMT SQL_INVALID_HANDLE SQL_NO_DATA SQL_NO_DATA_FOUND SQL_NTS SQL_NULL_DATA SQL_NULL_HANDLE SQL_NULL_HDBC SQL_NULL_HDESC SQL_NULL_HENV SQL_NULL_HSTMT SQL_SUCCESS SQL_SUCCESS_WITH_INFO =head1 Functions The functions are very similar to the ODBC C API. Parameters listed as [Output] in the ODBC 3.x API documentation are omitted when calling the wrapper versions. Functions with Output parameters return an array. The first value of the array is always the SQLRETURN value (SQL_SUCCESS, SQL_ERROR, etc). The remaining values in the array are the Output parameters in the order they appear in the ODBC API function call. Function without an Output parameter simply return the SQLRETURN value. In some cases, [Input] parameters are also omitted. This is because the wrapper substitutes specific values for some parameters. The list of functions exposed through the wrapper follows. The parameter names listed match those of the ODBC API documentation and have the same meaning. =over 4 =item SQLAllocHandle(HandleType, InputHandle) Returns an array: (SQLRETURN, OutputHandle). The wrapper will automatically set the ODBC environment to SQL_OV_ODBC3 when an environment handle is allocated. =item SQLCloseCursor(StatementHandle) =item SQLConnect(StatementHandle,ServerName,UserName,Authentication) =item SQLDisconnect(ConnectionHandle) =item SQLDriverConnect(ConnectionHandle,InConnectString,StringLength) SQL_NTS constant may be substituted for StringLength. Returns an array: (SQLRETURN, OutConnectionString, StringLength). Note that the driver completion/ dialog window options are not available. =item SQLExecDirect(StatementHandle, StatementText, TextLength) SQL_NTS constant may be substituted for TextLength. =item SQLFetch(StatementHandle) =item SQLFreeHandle(HandleType, Handle) =item SQLGetData(StatementHandle, ColumnNumber) Returns an array: (SQLRETURN, TargetValue, StrLen_or_Ind). The wrapper always fetches data as SQL_C_CHAR, so TargetValue is a string and StrLen_or_Ind is the number of characters. Note that ODBC Drivers are required to support conversion from any data type to SQL_C_CHAR, so data conversion issues should not be a problem. If the data value is NULL, TargetValue is the null string and StrLen_or_Ind is SQL_NULL_DATA. =item SQLGetDiagRec(HandleType, Handle, RecNumber) Returns an array: (SQLRETURN, sqlState, NativeError, MessageText, TextLength). =item SQLNumResultCols(StatementHandle) Returns an array: (SQLRETURN, ColumnCount). =back =head1 AUTHOR Jake Brutlag, jakeb@corp.webtv.net =head1 SEE ALSO perl(1). =cut cricket-1.0.5/util/LeanODBC/LeanODBC.xs0100755000114300000000000001416207770173524016301 0ustar bertdwheel#include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include "stripsql.h" static int not_here(char *s) { croak("%s not implemented on this architecture", s); return -1; } static double constant(char *name, int arg) { errno = 0; switch (*name) { case 'A': break; case 'B': break; case 'C': break; case 'D': break; case 'E': break; case 'F': break; case 'G': break; case 'H': break; case 'I': break; case 'J': break; case 'K': break; case 'L': break; case 'M': break; case 'N': break; case 'O': break; case 'P': break; case 'Q': break; case 'R': break; case 'S': if (strEQ(name, "SQL_ERROR")) #ifdef SQL_ERROR return SQL_ERROR; #else goto not_there; #endif if (strEQ(name, "SQL_HANDLE_DBC")) #ifdef SQL_HANDLE_DBC return SQL_HANDLE_DBC; #else goto not_there; #endif if (strEQ(name, "SQL_HANDLE_DESC")) #ifdef SQL_HANDLE_DESC return SQL_HANDLE_DESC; #else goto not_there; #endif if (strEQ(name, "SQL_HANDLE_ENV")) #ifdef SQL_HANDLE_ENV return SQL_HANDLE_ENV; #else goto not_there; #endif if (strEQ(name, "SQL_HANDLE_STMT")) #ifdef SQL_HANDLE_STMT return SQL_HANDLE_STMT; #else goto not_there; #endif if (strEQ(name, "SQL_INVALID_HANDLE")) #ifdef SQL_INVALID_HANDLE return SQL_INVALID_HANDLE; #else goto not_there; #endif if (strEQ(name, "SQL_NO_DATA")) #ifdef SQL_NO_DATA return SQL_NO_DATA; #else goto not_there; #endif if (strEQ(name, "SQL_NO_DATA_FOUND")) #ifdef SQL_NO_DATA_FOUND return SQL_NO_DATA_FOUND; #else goto not_there; #endif if (strEQ(name, "SQL_NTS")) #ifdef SQL_NTS return SQL_NTS; #else goto not_there; #endif if (strEQ(name, "SQL_NULL_DATA")) #ifdef SQL_NULL_DATA return SQL_NULL_DATA; #else goto not_there; #endif if (strEQ(name, "SQL_NULL_HANDLE")) #ifdef SQL_NULL_HANDLE return SQL_NULL_HANDLE; #else goto not_there; #endif if (strEQ(name, "SQL_NULL_HDBC")) #ifdef SQL_NULL_HDBC return SQL_NULL_HDBC; #else goto not_there; #endif if (strEQ(name, "SQL_NULL_HDESC")) #ifdef SQL_NULL_HDESC return SQL_NULL_HDESC; #else goto not_there; #endif if (strEQ(name, "SQL_NULL_HENV")) #ifdef SQL_NULL_HENV return SQL_NULL_HENV; #else goto not_there; #endif if (strEQ(name, "SQL_NULL_HSTMT")) #ifdef SQL_NULL_HSTMT return SQL_NULL_HSTMT; #else goto not_there; #endif if (strEQ(name, "SQL_SUCCESS")) #ifdef SQL_SUCCESS return SQL_SUCCESS; #else goto not_there; #endif if (strEQ(name, "SQL_SUCCESS_WITH_INFO")) #ifdef SQL_SUCCESS_WITH_INFO return SQL_SUCCESS_WITH_INFO; #else goto not_there; #endif break; case 'T': break; case 'U': break; case 'V': break; case 'W': break; case 'X': break; case 'Y': break; case 'Z': break; } errno = EINVAL; return 0; not_there: errno = ENOENT; return 0; } MODULE = LeanODBC PACKAGE = LeanODBC double constant(name,arg) char * name int arg short SQLAllocHandle(HandleType, InputHandle) short HandleType void* InputHandle PREINIT: void* OutputHandle; short rc; PPCODE: rc = SQLAllocHandle(HandleType,InputHandle,&OutputHandle); if (rc == SQL_SUCCESS && HandleType == SQL_HANDLE_ENV) { SQLSetEnvAttr(OutputHandle,SQL_ATTR_ODBC_VERSION,(void*)SQL_OV_ODBC3,NULL); } EXTEND(SP, 2); PUSHs(sv_2mortal(newSViv(rc))); PUSHs(sv_2mortal(newSViv((long) OutputHandle))); short SQLFreeHandle(HandleType, Handle) short HandleType void* Handle short SQLDisconnect(ConnectionHandle) void* ConnectionHandle short SQLGetDiagRec(HandleType, Handle, RecNumber) short HandleType void* Handle short RecNumber PREINIT: char sqlState[6]; long NativeError; char MessageText[1024]; short TextLength, rc; PPCODE: rc = SQLGetDiagRec(HandleType,Handle,RecNumber, (unsigned char*)sqlState,&NativeError, (unsigned char*)MessageText,1024,&TextLength); EXTEND(SP,5); PUSHs(sv_2mortal(newSViv(rc))); PUSHs(sv_2mortal(newSVpv(sqlState,5))); PUSHs(sv_2mortal(newSViv(NativeError))); PUSHs(sv_2mortal(newSVpv(MessageText,TextLength))); PUSHs(sv_2mortal(newSViv(TextLength))); short SQLDriverConnect(hdbc,szConnectStrIn,cbConnectStrIn) void* hdbc char* szConnectStrIn short cbConnectStrIn PREINIT: short cbConnStrOut; char szConnectStrOut[1024]; short rc; PPCODE: rc = SQLDriverConnect(hdbc,NULL, (unsigned char*)szConnectStrIn, cbConnectStrIn, (unsigned char*)szConnectStrOut, 1024,&cbConnStrOut,SQL_DRIVER_NOPROMPT); EXTEND(SP,3); PUSHs(sv_2mortal(newSViv(rc))); PUSHs(sv_2mortal(newSVpv(szConnectStrOut,cbConnStrOut))); PUSHs(sv_2mortal(newSViv(cbConnStrOut))); short SQLExecDirect(StatementHandle, StatementText, TextLength) void* StatementHandle unsigned char* StatementText long TextLength short SQLFetch(StatementHandle) void* StatementHandle short SQLNumResultCols(StatementHandle) void* StatementHandle PREINIT: short ColumnCount, rc; PPCODE: rc = SQLNumResultCols(StatementHandle, &ColumnCount); EXTEND(SP,2); PUSHs(sv_2mortal(newSViv(rc))); PUSHs(sv_2mortal(newSViv(ColumnCount))); short SQLGetData(StatementHandle, ColumnNumber) void* StatementHandle unsigned short ColumnNumber PREINIT: char TargetValue[1024] = "dummy"; long StrLen_or_Ind, rc; PPCODE: rc = SQLGetData(StatementHandle,ColumnNumber,SQL_C_CHAR, (SQLPOINTER) TargetValue,1024,&StrLen_or_Ind); EXTEND(SP,3); PUSHs(sv_2mortal(newSViv(rc))); if (StrLen_or_Ind > 0) { PUSHs(sv_2mortal(newSVpv(TargetValue,StrLen_or_Ind))); } else { TargetValue[0] = '\0'; PUSHs(sv_2mortal(newSVpv(TargetValue,0))); } PUSHs(sv_2mortal(newSViv(StrLen_or_Ind))); short SQLCloseCursor(StatementHandle) void* StatementHandle short SQLConnect(StatementHandle,ServerName,UserName,Authentication) void* StatementHandle unsigned char* ServerName unsigned char* UserName unsigned char* Authentication CODE: RETVAL = SQLConnect(StatementHandle, ServerName, SQL_NTS, UserName, SQL_NTS, Authentication, SQL_NTS); OUTPUT: RETVAL cricket-1.0.5/util/LeanODBC/Makefile.PL0100755000114300000000000000111407770173524016361 0ustar bertdwheeluse ExtUtils::MakeMaker; # See lib/ExtUtils/MakeMaker.pm for details of how to influence # the contents of the Makefile that is written. WriteMakefile( 'NAME' => 'LeanODBC', 'VERSION_FROM' => 'LeanODBC.pm', # finds $VERSION 'LIBS' => ['-lc:\vc98\lib\odbc32.lib -lc:\vc98\lib\kernel32.lib'], 'DEFINE' => '', # e.g., '-DHAVE_SOMETHING' 'INC' => '', # e.g., '-I/usr/include/other' ($] ge '5.005') ? ( 'AUTHOR' => 'Jake Brutlag (jakeb@corp.webtv.net)', 'ABSTRACT' => 'Thin wrapper around a restricted set of the ODBC 3.x API', ) : (), ); cricket-1.0.5/util/LeanODBC/README0100755000114300000000000000415107770173524015273 0ustar bertdwheelREADME for LEANODBC =================== Version 0.9 03-01-2001 This extension is a thin wrapper to a restricted subset of the ODBC 3.x API. It is not object-oriented and the functions share the same names as their ODBC 3.x counterparts. The restricted subset of the API only includes Core Interface functions for read-only operations. Compiling --------- This Perl extension includes a DLL which must be compiled using same version of Perl with which it will run. This means that a version compiled with ActiveState 5xx builds (Perl 5.00x) will not run with ActiveState 6xx builds (Perl 5.6). To compile you must have the Microsoft Visual C++ command line tools (compiler, linker, etc) installed and configured (i.e. run VCVARS32.BAT included with the Visual C++ distribution) and the libraries odbc32.lib and kernel32.lib included with Visual C++ 5.0 or greater. Edit the file Makefile.PL and change the line: 'LIBS' => ['-lc:\vc98\lib\odbc32.lib -lc:\vc98\lib\kernel32.lib'], to match the paths of these libraries on your system. Run: perl Makefile.PL nmake Installing ---------- Once the extension has compiled successfully, to install run: nmake install This installs to the local machine. To install remotely, you can make an ActiveState Perl Package and then use ppm. Follow the directions at http://velocity.activestate.com/docs/ActivePerl/faq/ActivePerl-faq2.html. Documentation ------------- To make HTML documentation, run: pod2html LeanODBC.pm > LeanODBC.html Testing ------- Before running the test, you need to configure an ODBC Data Source to point to the Northwind sample database included with Microsoft Office. Use the ODBC Control Panel applet (under Administrative Tools on Windows 2000 by default). Create a data source named 'NORTHWIND' pointing to the Access *.mdb file. To run the test, use: nmake test If you don't have Microsoft Office installed and you are familiar with ODBC and SQL, you can modify the test.pl file to use an ODBC Data Source in your environment. License ------- Cricket license terms apply to this package, LeanODBC. LeanODBC is Copyright (c) 2001 by Jake Brutlag cricket-1.0.5/util/LeanODBC/stripsql.h0100755000114300000000000000616507770173524016454 0ustar bertdwheel #ifdef __cplusplus extern "C" { /* Assume C declarations for C++ */ #endif /* __cplusplus */ /* ODBC function return values */ #define SQL_SUCCESS 0 #define SQL_SUCCESS_WITH_INFO 1 #define SQL_NO_DATA 100 #define SQL_ERROR (-1) #define SQL_INVALID_HANDLE (-2) #define SQL_NO_DATA_FOUND 100 /* null-terminated string code */ #define SQL_NTS (-3) /* handle type identifiers */ #define SQL_HANDLE_ENV 1 #define SQL_HANDLE_DBC 2 #define SQL_HANDLE_STMT 3 #define SQL_HANDLE_DESC 4 /* null handles returned by SQLAllocHandle() */ #define SQL_NULL_HENV 0 #define SQL_NULL_HDBC 0 #define SQL_NULL_HSTMT 0 #define SQL_NULL_HDESC 0 #define SQL_NULL_HANDLE 0L /* typedefs & defines not exported via Perl */ typedef unsigned char SQLCHAR; typedef long SQLINTEGER; typedef short SQLSMALLINT; typedef unsigned short SQLUSMALLINT; typedef void * SQLPOINTER; typedef SQLSMALLINT SQLRETURN; typedef void* SQLHANDLE; typedef SQLHANDLE SQLHSTMT; typedef SQLHANDLE SQLHDBC; #define SQL_C_CHAR 1 #define SQL_ATTR_ODBC_VERSION 200 #define SQL_OV_ODBC3 3UL #define SQL_DRIVER_NOPROMPT 0 #define SQL_NULL_DATA (-1) #define SQL_API __stdcall SQLRETURN SQL_API SQLAllocHandle(SQLSMALLINT HandleType, SQLHANDLE InputHandle, SQLHANDLE *OutputHandle); SQLRETURN SQL_API SQLCloseCursor(SQLHSTMT StatementHandle); SQLRETURN SQL_API SQLConnect(SQLHDBC ConnectionHandle, SQLCHAR *ServerName, SQLSMALLINT NameLength1, SQLCHAR *UserName, SQLSMALLINT NameLength2, SQLCHAR *Authentication, SQLSMALLINT NameLength3); SQLRETURN SQL_API SQLDisconnect(SQLHDBC ConnectionHandle); SQLRETURN SQL_API SQLDriverConnect( SQLHDBC hdbc, SQLHANDLE hwnd, SQLCHAR *szConnStrIn, SQLSMALLINT cbConnStrIn, SQLCHAR *szConnStrOut, SQLSMALLINT cbConnStrOutMax, SQLSMALLINT *pcbConnStrOut, SQLUSMALLINT fDriverCompletion); SQLRETURN SQL_API SQLExecDirect(SQLHSTMT StatementHandle, SQLCHAR *StatementText, SQLINTEGER TextLength); SQLRETURN SQL_API SQLFetch(SQLHSTMT StatementHandle); SQLRETURN SQL_API SQLFreeHandle(SQLSMALLINT HandleType, SQLHANDLE Handle); SQLRETURN SQL_API SQLGetData(SQLHSTMT StatementHandle, SQLUSMALLINT ColumnNumber, SQLSMALLINT TargetType, SQLPOINTER TargetValue, SQLINTEGER BufferLength, SQLINTEGER *StrLen_or_Ind); SQLRETURN SQL_API SQLGetDiagRec(SQLSMALLINT HandleType, SQLHANDLE Handle, SQLSMALLINT RecNumber, SQLCHAR *Sqlstate, SQLINTEGER *NativeError, SQLCHAR *MessageText, SQLSMALLINT BufferLength, SQLSMALLINT *TextLength); SQLRETURN SQL_API SQLNumResultCols(SQLHSTMT StatementHandle, SQLSMALLINT *ColumnCount); SQLRETURN SQL_API SQLSetEnvAttr(SQLHANDLE EnvironmentHandle, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER StringLength); #ifdef __cplusplus } /* End of extern "C" { */ #endif /* __cplusplus */ cricket-1.0.5/util/LeanODBC/test.pl0100755000114300000000000000564607771711440015735 0ustar bertdwheel# Before `make install' is performed this script should be runnable with # `make test'. After `make install' it should work as `perl test.pl' ######################### We start with some black magic to print on failure. # Change 1..1 below to 1..last_test_to_print . # (It may become useful if the test is moved to ./t subdirectory.) BEGIN { $| = 1; print "1..15\n"; } END {print "not ok 1\n" unless $loaded;} use LeanODBC qw(:ALL); $loaded = 1; print "ok 1\n"; ######################### End of black magic. # Insert your test code below (better if it prints "ok 13" # (correspondingly "not ok 13") depending on the success of chunk 13 # of the test code): my @ret = SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE); print (($ret[0] == SQL_SUCCESS) ? "ok 2\n" : "not ok 2\n"); my $henv = $ret[1]; @ret = SQLAllocHandle(SQL_HANDLE_DBC,$henv); print (($ret[0] == SQL_SUCCESS) ? "ok 3\n" : "not ok 3 $ret[0]\n"); my $hdbc = $ret[1]; my ($dsn, $user, $pwd) = ('NORTHWIND','',''); @ret = SQLConnect($hdbc,$dsn,$user,$pwd); print (($ret[0] == SQL_SUCCESS) ? "ok 4\n" : "not ok 4\n"); if ($ret[0] != SQL_SUCCESS) { @ret = SQLGetDiagRec(SQL_HANDLE_DBC,$hdbc,1); print join(' ',@ret) . "\n"; print "Troubleshooting: Did you create an ODBC Data Source" . " for the MS Access 2000 database fpnwind.mdb called NORTHWIND?\n"; die "Aborting test"; } # test a basic SELECT and FETCH @ret = SQLAllocHandle(SQL_HANDLE_STMT,$hdbc); print (($ret[0] == SQL_SUCCESS) ? "ok 5\n" : "not ok 5\n"); my $hstmt = $ret[1]; # select bogus string in case the Northwind db changes records in the future # remember, we are testing the API not the DB @ret = SQLExecDirect($hstmt, 'SELECT \'dummy1\' as "Last Name", NULL as "First Name"' . ' FROM Employees', SQL_NTS); print (($ret[0] == SQL_SUCCESS) ? "ok 6\n" : "not ok 6\n"); if ($ret[0] != SQL_SUCCESS) { @ret = SQLGetDiagRec(SQL_HANDLE_STMT,$hstmt,1); print join(' ',@ret) . "\n"; } @ret = SQLNumResultCols($hstmt); print (($ret[0] == SQL_SUCCESS && $ret[1] == 2) ? "ok 7\n" : "not ok 7\n"); @ret = SQLFetch($hstmt); print (($ret[0] == SQL_SUCCESS) ? "ok 8\n" : "not ok 8\n"); if ($ret[0] != SQL_SUCCESS) { @ret = SQLGetDiagRec(SQL_HANDLE_STMT,$hstmt,1); print join(' ',@ret) . "\n"; } @ret = SQLGetData($hstmt,1); print (($ret[0] == SQL_SUCCESS && $ret[1] eq 'dummy1') ? "ok 9\n" : "not ok 9\n"); @ret = SQLGetData($hstmt,2); print (($ret[0] == SQL_SUCCESS && $ret[2] == SQL_NULL_DATA) ? "ok 10\n" : "not ok 10\n"); @ret = SQLCloseCursor($hstmt); print (($ret[0] == SQL_SUCCESS) ? "ok 11\n" : "not ok 11\n"); @ret = SQLFreeHandle(SQL_HANDLE_STMT,$hstmt); print (($ret[0] == SQL_SUCCESS) ? "ok 12\n" : "not ok 12\n"); @ret = SQLDisconnect($hdbc); print (($ret[0] == SQL_SUCCESS) ? "ok 13\n" : "not ok 13\n"); @ret = SQLFreeHandle(SQL_HANDLE_DBC,$hdbc); print (($ret[0] == SQL_SUCCESS) ? "ok 14\n" : "not ok 14\n"); @ret = SQLFreeHandle(SQL_HANDLE_ENV,$henv); print (($ret[0] == SQL_SUCCESS) ? "ok 15\n" : "not ok 15\n"); cricket-1.0.5/util/README.listInterfaces0100644000114300000000000000346107454436271016742 0ustar bertdwheel listInterfaces+ is an enhanced Cricket configuration generator listInterfaces+ is a new version of listInterfaces, which allows building a config interfaces file for a variety of devices, including the Redback SMS which was previously impossible to monitor in Cricket. It also allows retrieval of interface speeds via the ifSpeed OID. The new listInterfaces+ also works nicely on Ciscos, and has been generalized enough that it also works on some switches, e.g. Foundry ServerIrons (to map the basic interfaces, not the more sophisticated features.) It can use either the ifName or ifDescr OID as the Cricket "target" key, and either the ifDescr or ifAlias OID as the textual interface description. It also offers some handy options for automatically suppressing output of entries for irrelevant pseudo-interfaces such as Cisco's Loopback, BVI, or CEF-layer interfaces; this makes it easier to always build the cricket config files directly off the router. In general if you play around to get the right parameters, it ought to be usable with nearly any device you can bulk-walk and which supports *either* the standard SNMP OIDs for ifName or ifDescr as unique identifiers for all its interfaces. For instance, here's how I automatically regenerate interface definitions for a whole group of Cisco routers, 7200s, 3620s, 2600s, and 2524s: for router in *; do echo "Querying router $router for current interface list" $HOME/cricket/util/listInterfacesPlus+ --speed --suppress BVI \ --suppress Loop --suppress Null --suppress T1 --suppress cef \ $router $PASSWD > $router/interfaces done (On the Redback SMS only, it still may require some post-creation hand-editing of the config files until I can add some new code in, because it creates non-unique names for protocol layers on some physical ports.) cricket-1.0.5/util/convert-named-ds0100755000114300000000000001206207510160145016160 0ustar bertdwheel#!/usr/local/bin/perl -w # -*- perl -*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998-2002 Jeff R. Allen and WebTV Networks, Inc. # Copyright (C) 2002 Bert Driehuis # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. BEGIN { my $programdir = (($0 =~ m:^(.*/):)[0] || "./") . ".."; eval "require '$programdir/cricket-conf.pl'"; eval "require '/usr/local/etc/cricket-conf.pl'" unless $Common::global::gInstallRoot; $Common::global::gInstallRoot ||= $programdir; } use lib "$Common::global::gInstallRoot/lib"; use RRDs 1.000101; use Common::Version; use Common::global; use Common::Log; use Common::Options; use Common::Util; use Common::Map; use Common::HandleTarget; use ConfigTree::Cache; Common::Options::commonOptions(); $Common::global::gCT ||= new ConfigTree::Cache; $gCT = $Common::global::gCT; $gCT->Base($Common::global::gConfigRoot); $gCT->Warn(\&Warn); if (! $gCT->init()) { Die("Failed to open compiled config tree from " . "$Common::global::gConfigRoot/config.db: $!"); } # if they gave us no subtrees to focus on, use the root of the config tree if ($#ARGV+1 == 0) { push @ARGV, '/'; } # foreach subtree to do # find the base node of that subtree # foreach leaf node of this subtree # process it my($subtree); foreach $subtree (@ARGV) { if ($gCT->nodeExists($subtree)) { $gCT->visitLeafs($subtree, \&handleTarget, \&handleTargetInstance, \&localHandleTargetInstance); } else { Warn("Unknown subtree $subtree."); } } exit; # only use strict for the subroutines use strict; sub localHandleTargetInstance { my($name, $target) = @_; my($tname) = $target->{'auto-target-name'}; my($tpath) = $target->{'auto-target-path'}; # first, dump the dict, to help debug things my($k, $v, $t); $t = "target $tname\n"; foreach $k (sort (keys(%{$target}))) { next if ($k eq "auto-target-name"); $v = $target->{$k}; $v = "[undef]" if (! defined($v)); $t .= "\t$k = $v\n"; } Debug($t); # skip this if it's a meta-target if ((defined($target->{'targets'})) || (defined($target->{'mtargets'}))) { Debug("Skipping meta target $tname"); return; } my($datafile) = $target->{'rrd-datafile'}; if (!defined($datafile)) { Warn("Could not find a datafile for $tname."); return; } if (-f $datafile) { return unless fixRRD($name, $target); } } sub fixRRD { # Create a new RRD file base on the contents of the # referenced dictionary. my($name, $target) = @_; my($datafile) = $target->{'rrd-datafile'}; my($tname) = $target->{'auto-target-name'}; my($ttype) = lc($target->{'target-type'}); my($type, $val); my($valid) = 0; my(@dsDesc) = (); my($dsCnt) = 0; my(@arg) = ($datafile); my($ttRef) = $main::gCT->configHash($name, 'targettype', $ttype, $target); if (! $ttRef) { Warn("Unknown target type: $ttype."); return; } # first, process the ds tag my($dses) = $ttRef->{'ds'}; if (! $dses) { Warn("No ds tag found for target type $ttype."); return; } my($ds); foreach $ds (split(/\s*,\s*/, $dses)) { my($dsname) = lc($ds); my($d) = $main::gCT->configHash($name, 'datasource', $dsname, $target); if (defined($d)) { my $old_dsname; my $new_dsname; if ($Common::global::gLongDSName) { $old_dsname = "ds$dsCnt"; $new_dsname = Common::Util::mungeDsName($dsname); } else { $old_dsname = Common::Util::mungeDsName($dsname); $new_dsname = "ds$dsCnt"; } push @dsDesc, "-r", "$old_dsname:$new_dsname"; $dsCnt++; } else { Warn("Datasource $ds referenced by target " . "type $ttype does not exist."); return; } } push(@arg, @dsDesc); Info("Updating datafile $datafile for target name $tname."); Debug(join(' ', 'RRDs::tune', @arg)); RRDs::tune(@arg); if (my $error = RRDs::error()) { Warn("Cannot update $datafile: $error\n"); } return 1; } # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/util/dump-targets0100755000114300000000000000455007477775462015463 0ustar bertdwheel#!/usr/local/bin/perl -w # -*- perl -*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. BEGIN { my $programdir = (($0 =~ m:^(.*/):)[0] || "./") . ".."; eval "require '$programdir/cricket-conf.pl'"; eval "require '/usr/local/etc/cricket-conf.pl'" unless $Common::global::gInstallRoot; $Common::global::gInstallRoot ||= $programdir; } use lib "$Common::global::gInstallRoot/lib"; use Common::Options; use Common::Log; use Common::global; use ConfigTree::Cache; Common::Options::commonOptions(); Common::Log::setLevel('debug'); $Common::global::gCT = new ConfigTree::Cache; $gCT = $Common::global::gCT; $gCT->Base($Common::global::gConfigRoot); $gCT->Warn(\&Warn); if (! $gCT->init()) { Die("Failed to open compiled config tree from " . "$Common::global::gConfigRoot/config.db: $!"); } # if they gave us no subtrees to focus on, use the root of the config tree if ($#ARGV+1 == 0) { push @ARGV, '/'; } # foreach subtree to do # find the base node of that subtree # foreach leaf node of this subtree # process it my($subtree); foreach $subtree (@ARGV) { if ($gCT->nodeExists($subtree)) { $gCT->visitLeafs($subtree, \&myHandleTarget); } else { Warn("Unknown subtree $subtree."); } } exit; sub myHandleTarget { my($name) = @_; my($tpath, $tname) = ($name =~ /^(.*)\/(.*)$/); my($target) = $gCT->configHash($name, 'target', undef, 1); print "Target dictionary for $tname\n"; my($k, $v); foreach $k (sort (keys(%{$target}))) { next if ($k eq "tname"); $v = $target->{$k}; print "\t$k = $v\n"; } print "\n"; } cricket-1.0.5/util/generate-statics0100755000114300000000000001471107454671377016304 0ustar bertdwheel#!/usr/local/bin/perl -w # -*- perl -*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1999 Noam Freedman # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. BEGIN { my $programdir = (($0 =~ m:^(.*/):)[0] || "./") . ".."; eval "require '$programdir/cricket-conf.pl'"; eval "require '/usr/local/etc/cricket-conf.pl'" unless $Common::global::gInstallRoot; $Common::global::gInstallRoot ||= $programdir; } use lib "$Common::global::gInstallRoot/lib"; use Common::global; # You need to update this to point to the URL # you use to access Cricket. $gBaseURL = "http://localhost/~cricket/cricket/grapher.cgi"; use File::Basename; use LWP::UserAgent; use HTTP::Request; use HTTP::Response; use ConfigTree::Cache; use Common::HandleTarget; use Common::Map; use Common::Options; use Common::Log; Common::Options::commonOptions( 'baseURL=s' => \$gBaseURL ); initConst(); $Common::global::gCT = new ConfigTree::Cache; $gCT = $Common::global::gCT; $gCT->Base($Common::global::gConfigRoot); $gCT->Warn(\&Warn); if (! $Common::global::gCT->init()) { Die("Failed to open compiled config tree from " . "$Common::global::gConfigRoot/config.db: $!"); } # if they gave us no subtrees to focus on, use the root of the config tree if ($#ARGV+1 == 0) { push @ARGV, '/'; } my($subtree); foreach $subtree (@ARGV) { if ($gCT->nodeExists($subtree)) { $gCT->visitLeafs($subtree, \&handleTarget, \&handleTargetInstance, \&localHandleTargetInstance); } else { Warn("Unknown subtree $subtree."); } } exit; sub localHandleTargetInstance { my($Name, $target) = @_; $targetpath = $target->{'auto-target-path'}; $targetname = $target->{'auto-target-name'}; if (defined($target->{'generate-static'})) { Info("Working on target $targetname."); my($path,$name,$reqRanges,@ranges); $reqRanges = $target->{'static-ranges'}; if (defined($target->{'static-path'}) && defined($target->{'static-name'})) { $path = $target->{'static-path'}; $name = $target->{'static-name'}; my($range, @ranges); @ranges = getRanges($reqRanges); foreach $range (@ranges) { $rangeLabel = rangeToLabel($range); my($paramtarget) = "$targetpath/$targetname"; my($paraminst); if (defined($target->{'inst'})) { $paraminst = $target->{'inst'}; } my($paramrange) = $range; # DO DSLIST STUFF # find the ds names based on the target type my($ttype) = lc($target->{'target-type'}); my($ttRef) = $main::gCT->configHash($Name, 'targettype', $ttype, $target); # If there are views defined, then we generate graphs # for each view. my($dslist); if (defined($ttRef->{'view'})) { my($v); foreach $v (split(/\s*,\s*/, $ttRef->{'view'})) { # views are like this: "cpu: cpu1load cpu5load" my($vname, $dss) = split(/\s*:\s*/, $v, 2); $dslist = $dss; $dslist =~ s/\s*$//; $dslist =~ s/\s+/,/g; $URL = "$gBaseURL?type=gif&target=$paramtarget"; $URL .= "&dslist=$dslist&range=$paramrange"; if ($paraminst ne "") { $URL .= "&inst=$paraminst"; } Info("Retrieving graph for $name-$vname-$rangeLabel"); getURL($URL,"$path/$name-$vname-$rangeLabel.gif"); } } else { $dslist = $ttRef->{'ds'}; # squeeze out any extra spaces $dslist = join(',', split(/\s*,\s*/, $dslist)); $URL = "$gBaseURL?type=gif&target=$paramtarget"; $URL .= "&dslist=$dslist&range=$paramrange"; if ($paraminst ne "") { $URL .= "&inst=$paraminst"; } Info("Retrieving graph for $name-$rangeLabel"); getURL($URL,"$path/$name-$rangeLabel.gif"); } } } } return; } sub getRanges { my($scales) = @_; $scales = "d:w:m:y" unless (defined($scales)); # these definitions mirror how MRTG 2.5 sets up its graphs my(%scaleMap) = ( 'd' => $main::kHour * 42, 'w' => $main::kDay * 10, 'm' => $main::kWeek * 6, 'y' => $main::kMonth * 16); my($scale, @res); foreach $scale (split(/\s*:\s*/, $scales)) { # later, we might do more sophisticated scale specification $scale = $scaleMap{$scale}; push @res, $scale; } return @res; } sub initConst { $main::kMinute = 60; # 60 seconds/min $main::kHour = 60 * $main::kMinute;# 60 minutes/hr $main::kDay = 24 * $main::kHour; # 24 hrs/day $main::kWeek = 7 * $main::kDay; # 7 days/week $main::kMonth = 30 * $main::kDay; # 30 days/month $main::kYear = 365 * $main::kDay; # 365 days/year $main::kTypeUnknown = 0; $main::kTypeUnknown = 0; # shut up, -w. $main::kTypeDaily = 1; $main::kTypeWeekly = 2; $main::kTypeMonthly = 3; $main::kTypeYearly = 4; @main::gRangeNameMap = ( undef, 'Hourly', 'Daily', 'Weekly', 'Monthly' ); } sub rangeToLabel { my($range) = @_; return $main::gRangeNameMap[rangeType($range)]; } sub rangeType { my($range) = @_; my($rangeHours) = $range / 3600; # question: when is kTypeUnknown appropriate? if ($range < $main::kWeek) { return $main::kTypeDaily; } elsif ($range < $main::kMonth) { return $main::kTypeWeekly; } elsif ($range < $main::kYear) { return $main::kTypeMonthly; } else { return $main::kTypeYearly; } } sub getURL { my($url,$filename) = @_; Debug("Fetching url: $url"); my $ua = new LWP::UserAgent; my $request = new HTTP::Request('GET', $url); my $response = $ua->request($request); if ($response->is_success) { my($dir) = dirname($filename); if (! -d $dir) { Info("Making directory $dir to hold file $filename."); Common::Util::MkDir($dir); } if (!open(URL,">$filename")) { Error("Error writing to $filename: $!"); return; } print URL $response->content; close(URL); } else { Error("Error retrieving target graph: " . $response->message()); } } cricket-1.0.5/util/get-collector-stats0100755000114300000000000000517007757744460016740 0ustar bertdwheel#!/usr/local/bin/perl -w # -*- perl -*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. BEGIN { my $programdir = (($0 =~ m:^(.*/):)[0] || "./") . ".."; eval "require '$programdir/cricket-conf.pl'"; eval "require '/usr/local/etc/cricket-conf.pl'" unless $Common::global::gInstallRoot; $Common::global::gInstallRoot ||= $programdir; } use strict; use lib "$Common::global::gInstallRoot/lib"; use Common::global; use HTTP::Date; use RRDs; my $subtree = shift || die "Must provide a subtree.\n"; my @output = (); my $dur = 0; my $tnum = 0; my $when = 0; my $now = time(); if (! -f "$Common::global::gCricketHome/cricket-logs/$subtree.times") { die "Couldn't find $Common::global::gCricketHome/cricket-logs/$subtree.times.\n"; } else { @output = `tail -1 $Common::global::gCricketHome/cricket-logs/$subtree.times 2>&1`; if ($?) { die "Error tailing $Common::global::gCricketHome/cricket-logs/$subtree.times: $?\n"; } } if ($output[0] =~ /\[(.*?)\]\s+Processed\s+([0-9]+)\s+targets\s+in\s+(.*)\.$/) { my $date = 0; $date = str2time($1), $tnum = $2, $dur = $3; $dur = &durationToSeconds($dur); $when = $date - $dur; # if the log file hasn't been updated, send back the special time "N" if ($when < $now - 301) { $when = "N"; } } else { die "Bad line in $subtree.times: $output[0]"; } if ($when ne 'N') { printf("%d\@%s\n", $dur, $when); printf("%d\@%s\n", $tnum, $when); printf("%.2f\@%s\n", $tnum/$dur, $when); } else { printf("%d\n", $dur); printf("%d\n", $tnum); printf("%.2f\n", $tnum/$dur); } exit(0); sub durationToSeconds { my $durLong = shift; my $durSecs = 0; $durSecs += $1 * 60 if $durLong =~ m/([0-9]+)\s+minutes/; $durSecs += $1 if $durLong =~ m/([0-9]+)\s+seconds/; if ($durSecs == 0) { warn "Duration $durLong resolved to 0 seconds.\n"; $durSecs = 1; } return($durSecs); } cricket-1.0.5/util/listInterfaces0100755000114300000000000001555507454671377016030 0ustar bertdwheel#!/usr/local/bin/perl -w # -*- perl -*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998-2002 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. BEGIN { my $programdir = (($0 =~ m:^(.*/):)[0] || "./") . ".."; eval "require '$programdir/cricket-conf.pl'"; eval "require '/usr/local/etc/cricket-conf.pl'" unless $Common::global::gInstallRoot; $Common::global::gInstallRoot ||= $programdir; } use lib "$Common::global::gInstallRoot/lib"; use strict; use snmpUtils; use Common::Options; use Common::Log; # Default the log level high enough to ignore warnings, unless they # ask for them with "./listInterfaces -loglevel warn". This is so # that when we get warnings for trying to talk to extended mibs, the # user does not freak out. unshift @ARGV, "-logLevel", "error"; my(%suppress) =(); my($bytes) = 0; my($usage) = 0; my($redback) = 0; my($cisco) = 0; my($useifnamekey) = 0; my($useifdesckey) = 0; my($usealias) = 0; my($usedescr) = 0; my($units) = "bits"; my($showspeed) = 0; Common::Options::commonOptions( "speed!", \$showspeed, "ifname!", \$useifnamekey, "ifdesc!", \$useifdesckey, "alias!", \$usealias, "descript!", \$usedescr, "redback!", \$redback, "cisco!", \$cisco, "bytes|b!", \$bytes, "help|usage|h|?!", \$usage, "suppress=s@", \@main::suppress ); if ($cisco && $redback) { $usage = 1; } elsif ($cisco) { $showspeed = 1; $useifdesckey = 1; $usealias = 1; $useifnamekey = 0; $usedescr = 0; } elsif ($redback) { $showspeed = 1; $useifnamekey = 1; $usedescr = 1; $useifdesckey = 0; $usealias = 0; } else { $useifdesckey = 1 if ! $useifnamekey; $usealias = 1 if ! $usedescr; } if ($#ARGV+1 < 1 || $#ARGV+1 > 2 || $usage) { print STDERR "Usage: $0 [options] hostname [community]\n"; print STDERR "\tcommunity will default to public if you don't give it.\n\n"; print STDERR "Options:\n"; print STDERR " --help or --usage or -? show this message\n"; print STDERR " --speed \tinclude interface speed in output (platform-dependent)\n"; print STDERR " --ifdesc \tuse ifDescr as interface key (default)\n"; print STDERR " --ifname \tuse ifName as interface key instead of ifDescr\n"; print STDERR " --alias \tuse ifAlias as interface description (default)\n"; print STDERR " --descr \tuse ifDescr as interface description\n"; print STDERR " --bytes \tuse units of bytes (default bits)\n"; print STDERR " --cisco \tsame as --speed --ifdesc --alias \n"; print STDERR " --redback \tsame as --speed --ifname --descr \n"; print STDERR " --suppress [pattern] \tignore/skip interfaces matching pattern\n"; print STDERR " \t\tMultiple --suppress entries may be used.\n\n"; print STDERR "Examples:\n"; print STDERR " $0 --cisco --suppress BVI --suppress Loop mycisco mySNMP\n";; print STDERR "Generate interfaces config for a Cisco, including speed information \n"; print STDERR "per interface but skipping loopback and BVI (bridge) interfaces \n"; exit 1; } $units = "bytes" if $bytes; my($router) = $ARGV[0]; my($community) = "public"; $community = $ARGV[1] unless (!defined($ARGV[1])); my($snmp) = "$community\@$router"; print "target --default--\n"; print " router = $router\n"; print "\n"; my($ifDescr) = '1.3.6.1.2.1.2.2.1.2'; my($ifName) = '1.3.6.1.2.1.31.1.1.1.1'; my($ifAlias) = '1.3.6.1.2.1.31.1.1.1.18'; my($interface_key) = '.' . $ifDescr; $interface_key = '.' . $ifName if $useifnamekey; my($interface_name) = $ifAlias; $interface_name = $ifDescr if $usedescr; ## print("# Read with ifName key $interface_key\n") if $useifnamekey; my($outputme) = 0; my($test)= ""; my($row); foreach $row (snmpUtils::walk($snmp, $interface_key)) { my($oid, $value) = split(/:/, $row, 2); $oid =~ s/$interface_key//; my($stat) = snmpUtils::get($snmp, "1.3.6.1.2.1.2.2.1.7.$oid"); my($connector) = snmpUtils::get($snmp, "1.3.6.1.2.1.31.1.1.1.17.$oid"); my($desc) = snmpUtils::get($snmp, "$interface_name.$oid"); my($ifSpeed) = snmpUtils::get($snmp, "1.3.6.1.2.1.2.2.1.5.$oid"); # Sanitize $desc value because current Redback SNMP implementation # as of 11/2000 returns corrupted garbage in ifDescr sometimes. $desc =~ tr/!-~/ /cs; # convert all non-printable ASCIIs to a space # second fetched value is oper status -- use it to decide which # targets to print $outputme = ($stat == 1); # suppress array is list of strings to be pattern-matched to # decide whether we ignore this interface. TEST: foreach $test (@main::suppress) { if ($outputme && $value =~ m/$test/) { $outputme = 0; last TEST; } } if ($outputme) { my($target) = $value; $target =~ s/[\/\s:]/\_/g; print "target $target\n"; output("interface-name", $value); output("short-desc", $desc); if ($showspeed && $ifSpeed) { output("if-speed", $ifSpeed); output("fmt-speed", fmispeed($ifSpeed, $units)); } # experience shows that subinterfaces (like those # representing the endpoints of PVCs riding on a # frame relay link) need a special config. if (defined($connector) && $connector == 2) { output("target-type", "sub-interface"); } print "\n"; } } sub output { my($name, $value) = @_; my($quote) = ''; if (defined($value)) { # quote empty (or white-space only) lines, or lines which # will have embedded spaces. $quote = '"' if ($value =~ /^\s*$/ || $value =~ /\s/); print "\t$name = $quote$value$quote\n"; } } sub fmispeed { my($number, $units) = @_; my @short; if ($units eq "bytes") { @short = ("Bytes/sec","kBytes/sec","MBytes/sec","GBytes/sec"); } else { @short = ("bits/sec","kbits/sec","Mbits/sec","Gbits/sec"); } my $digits = length("".$number); my $divm = 0; while ($digits - $divm*3 > 3) { $divm++; } my $divnum = $number/10**($divm*3); if ($divnum == int $divnum) { return sprintf("%1d %s", $divnum, $short[$divm]); } return sprintf("%1.3f %s", $divnum, $short[$divm]); } cricket-1.0.5/util/metaQuery.pl0100755000114300000000000003177110021715355015410 0ustar bertdwheel#!/usr/bin/perl -w # -*- perl -*- ############################################################################## # # metaQuery.pl - Template to use cricket meta data for communicating with # external network monitoring tools. # # Copyright (C) 2000 Mike Fisher and Tech Data Corporation # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # # Created: 04/03/2004 - Francois Mikus ( fmikus _AT_ acktomic dot com) # BEGIN { my $programdir = (($0 =~ m:^(.*/):)[0] || "./") . ".."; eval "require '$programdir/cricket-conf.pl'"; eval "require '/usr/local/etc/cricket-conf.pl'" unless $Common::global::gInstallRoot; $Common::global::gInstallRoot ||= $programdir; } use lib "$Common::global::gInstallRoot/lib"; use strict; use Common::Options; use Common::Log; use ConfigTree::Cache; use RRD::File; Common::Log::setFormat('minimal'); $Common::global::gCT = new ConfigTree::Cache; my ($gCT) = $Common::global::gCT; $gCT->Base($Common::global::gConfigRoot); $gCT->Warn(\&Warn); if (! $gCT->init()) { Die("Failed to open compiled config tree from " . "$Common::global::gConfigRoot/config.db: $!"); } my $VERSION = '0.9'; my ($loglevel) = 'info'; my ($devicename); my (@trees); # Set the default logging level Common::Log::setLevel($loglevel); usage() if (!$ARGV[0]); while ($ARGV[0] && $ARGV[0] =~ /^-/) { my $arg = shift; if ($arg eq '-h' || $arg eq '--help') { usage(); } elsif ($arg eq '--loglevel') { my $x = shift; Die ("Missing a value for argument --loglevel\nUse --help") if (!$x); $loglevel = $x; } elsif ($arg eq '--all') { push @trees, '/'; } elsif ($arg eq '--subtree') { my $x = shift; Die ("Missing a value for argument --subtree\nUse --help") if (!$x); push @trees, $x; } elsif ($arg eq '--version') { version(); } else { Die ("Unknown flag: $arg\nUse --help."); } } Common::Log::setLevel($loglevel); sub usage { #' print STDERR < 'if_err', 'errors-in' => 'if_err', 'errors-out' => 'if_err', 'errors-resets' => 'if_err', 'errors-crc' => 'if_err', 'congestion-FECN-in' => 'if_err', 'congestion-BECN-in' => 'if_err', 'bandwidth-in' => 'if_util', 'bandwidth-out' => 'if_util', 'congestion-in' => 'if_util', 'congestion-out' => 'if_util', 'rate-limit' => 'if_util', 'rtt-generic-exceed' => 'rtt', 'rtt-ftp-exceed' => 'rtt-ftp', 'rtt-ftp-warn' => 'rtt-ftp', 'rtt-http-exceed' => 'rtt-http', 'rtt-generic-fail' => 'rtt', 'rtt-ftp-fail' => 'rtt-ftp', 'rtt-http-fail' => 'rtt-http', 'memory-warn' => 'memory', 'memory-alert' => 'memory', 'cpu-warn' => 'cpu', 'cpu-alert' => 'cpu', 'anomalie-in' => 'if_ano', 'anomalie-out' => 'if_ano', 'cpu' => 'cpu', 'memory' => 'memory', 'unclassified' => 'unclassified', ); # Global hashes that will be used to store the values of the monitor-thresholds my %gData; my %gDevices; # This URL pre supposes that you are using pathinfo urls # $gUrlStyle="pathinfo"; # URL path for authentication my $gCRICKET = "http://127.0.0.1/cricket/grapher.cgi"; # foreach subtree to do # find the base node of that subtree # foreach leaf node of this subtree # process it my($subtree); foreach $subtree (@trees) { if ($gCT->nodeExists($subtree)) { $gCT->visitLeafs($subtree, \&myHandleTarget); } else { Warn("Unknown subtree $subtree."); } } my (@modulelist) = createModuleList(%mapErrorToModule); sendEvents(); # Advanced debug loop #foreach my $refData (keys %gData) { # # Debug("@{$gData{$refData}}"); #} 1; sub myHandleTarget { my($name) = @_; my(@monitor_instances); # Consult the config tree cache to get the information on that # specific target. my($tpath, $tname) = ($name =~ /^(.*)\/(.*)$/); my($target) = $gCT->configHash($name, 'target', undef, 1); # Devices should use the snmp-host variable to indicate the name # the device should be referred to. my ($targetname) = $target->{'snmp-host'}; # Skip targets that have the nostate variable set to true return if (defined $target->{'nostate'} && $target->{'nostate'} eq 'true'); # We are only interested in targets that have monitor-thresholds # defined in the config-tree of the META type. return unless ($target->{'monitor-thresholds'} && $target->{'monitor-thresholds'} =~ /:META:/); Debug ("$target->{'monitor-thresholds'}"); Debug ("### TargetName $tname ###"); ### Start meta file processing # find out the location of the meta data file my($rrd) = new RRD::File( -file=>$target->{'rrd-datafile'} ); # Look for active meta data my($refMeta) = $rrd->getMeta(); foreach my $key (keys %{$refMeta}) { Debug ("Thresholds key: $key data: $refMeta->{$key}"); } ### End meta file processing my ($a, $subtree, @path) = split (/\//, $tpath); # Creating a path that is unique up to the point of the targetname my ($shortpath) = split (/$targetname/, $name); $shortpath .= $targetname; # Rename the devicename to the Config-Tree path which will be used to refer to all # the different hashes + the logical name of the network device, # these will be used to build the HTTP path to the Cricket grapher.cgi and to reference the device my ($devicename) = $targetname; my ($instance) = mapinstance($targetname, @path); if (defined $instance) { $tname = $instance . "=" . $tname; Debug ("Found an instance, update TargetName: $tname"); } # Take a note of all the distinct routers for which we found monitor-thresholds # This will make it easier to process the devices only once. if (!exists $gDevices{$devicename}) { $gDevices{$devicename} = $shortpath; } # Process each monitor threshold and create an entry containing # the message, references to active alarms, the ds # and the device it belongs to. my (@monitors) = split (/,\s+/, $target->{'monitor-thresholds'}); my (%monitors); my ($cnt) = 0; foreach my $monitor (@monitors) { $cnt += 1; next unless $monitor =~ /:META:/; # Seperate both sides, as they could have a variable number of arguments my ($thresh_args, $meta_args) = split(/:META:/,$monitor); my (@thresh_args) = split(/:/,$thresh_args); # Process optional META options contained in the monitor threshold my (@meta_args) = split(/:/,$meta_args); my ($errorname) = shift(@meta_args); my ($colour) = shift(@meta_args); $colour = 'green' if (!$refMeta->{$monitor}); my ($ds) = $thresh_args[0]; # Do not unshift as we want to use the variable later # Convert floats with excessive precision to something more digestible my $value = cleanupValue($refMeta->{$monitor}) if $refMeta->{$monitor}; # Replace the colour from the monitor with purple if data could not be retrieved # by the monitor threshold engine, this is indicated by NaN. $colour = 'purple' if ($value && $value =~ /NaN/i); my $module = $mapErrorToModule{$errorname}; Warn("Invalid or unset monitor threshold errorname, $errorname, for $devicename\nExpected format: :META::") if (!$module); #next if (!$module); # Uncomment this for strict processing of errornames $module = "unclassified" if (!$module); my $html_path = getHtmlPath($tname, $devicename, $shortpath, $module, $ds); my ($message) = "$errorname, OK."; $message = "$errorname on value $value, for $thresh_args of $tname." if ($value); # Build a unique key with == seperator tokens, and store the data as an anonymous array $gData{$devicename ."==". $module ."==". $cnt} = [$html_path, $ds, $colour, $message]; Info("key: " . $devicename ."==". $module ."==". $cnt . "value: " . $html_path . " " . $ds . " " . $colour . " " . $message); } } sub processKey { my ($path, $devicename) = @_; my ($division,$location,$rest) = split (/-/, $devicename); my (@path) = split (/\//,$path); my ($href) = "$devicename"; last; } if ($e ne "") { $href .= "/$e"; } } return ($division,$location,$devicename,$href); } sub instanceToHREF { my ($path, $devicename, $instance, $view) = @_; my (@instance) = split(/=/, $instance); my (@path) = split (/\//,$path); my ($division,$location,$rest) = split (/-/, $devicename); my ($href_instance) = "$devicename @instance"; return ($href_instance); } sub mapinstance { my ($targetname) = shift; my (@path) = @_; my ($instance); # Instance name and slot number if applicable for Catalyst CatOS Switches and others foreach my $c (reverse (@path)) { if (($c eq $targetname) || (!defined $c)){ chop $instance unless (!defined $instance); last; } else { $instance .= $c . "-"; } } return ($instance); } sub cleanupValue { my ($value) = @_; return if (!$value); ($value) = $value =~ /\s*value (\S+)$/; return "undef" if (!$value); # Return the value if it is not a floating point return ($value) if ($value !~ /^\d+\.\d+$/); # Remove extraneous precision from floating point variables return (sprintf("%.0f ", $value)) if ($value > 100); return (sprintf("%.2f ", $value)) if ($value > 1); return (sprintf("%.3f ", $value)); } sub createModuleList { my (%mapping) = @_; my (%uniqueModule); foreach my $error (keys %mapping) { $uniqueModule{$mapping{$error}} = 1 if (!exists $uniqueModule{$mapping{$error}}); } my (@placeholder) = (keys %uniqueModule); return @placeholder; } sub getHtmlPath { my ($tname, $devicename, $shortpath, $module, $ds) = @_; return $devicename; } ################################################################################ ### This sub should process the events in whatever format is required ### and send to the event management system. ################################################################################ sub sendEvents { # Do something useful with the compilation of error messages, status and html links. # Send that useful compilation to an event management system. } cricket-1.0.5/util/newsstats0100755000114300000000000000072507047764267015074 0ustar bertdwheel#!/usr/local/bin/perl ## # Get simple stats from INND with cleanfeed # expects output crom "ctlinnd mode" on STDIN # # Author: Jost Krieger # Revision: $Revision: 1.1.1.1 $ ## use strict; my ($passed, $reject, $refuse); while (<>) { # print STDERR $_; if (/Perl filter stats/) { /Pass: (\d+)/ and $passed=$1; /Reject: (\d+)/ and $reject=$1; /Refuse: (\d+)/ and $refuse=$1; } } print "$passed\n$reject\n$refuse\n"; cricket-1.0.5/util/perfInfo.pl0100644000114300000000000002607007771711437015214 0ustar bertdwheel#!/usr/local/bin/perl -w # -*- perl -*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. BEGIN { my $programdir = (($0 =~ m:^(.*/):)[0] || "./") . ".."; eval "require '$programdir/cricket-conf.pl'"; eval "require '/usr/local/etc/cricket-conf.pl'" unless $Common::global::gInstallRoot; $Common::global::gInstallRoot ||= $programdir; } use lib "$Common::global::gInstallRoot/lib"; use strict; use Win32::PerfLib; use Getopt::Long; my ($counter,$pr) = ({},{}); my $server; my ($object,$cname,$ctype,$instance,$debug) = 0; my %rcounter; &GetOptions( "s|server=s" => \$server, "o|object=s" => \$object, "c|counter=s" => \$cname, "i|instance=s" => \$instance, "d|debug=i" => \$debug, "h|help" => \&Help, ); Win32::PerfLib::GetCounterNames($server, $counter); if(!defined($server)) { $server = "."; } if(! $object || ! $counter) { warn "Error: -o|-c must be defined!\n"; &Help; } print "* Setting server to $server\n"; foreach my $k (keys %{$counter}) { $rcounter{lc($counter->{$k})} = $k; } my $pl = new Win32::PerfLib($server); $pl->GetObjectList($rcounter{$object}, $pr) || die "Can't get counters from $server because of: $!\n"; $pl->Close(); print "* Getting counter type(s) for $cname\n"; foreach my $level2 (keys %{$pr->{'Objects'}}) { if($pr->{'Objects'}->{$level2}->{'NumInstances'} > 0) { if(defined($instance)) { print "\t* Traversing multiple counters for $cname\n"; foreach my $level4 (keys %{$pr->{'Objects'}->{$level2}->{'Instances'}}) { my $ir = $pr->{'Objects'}->{$level2}->{'Instances'}->{$level4}; foreach my $level5 (keys %{$ir->{'Counters'}}) { my $cr = $ir->{'Counters'}->{$level5}; my $saneCounter = $counter->{$cr->{'CounterNameTitleIndex'}}; my $saneInstance = $ir->{'Name'}; print "\t\t\t* Debug: $saneCounter->$saneInstance\n" if($debug == 1); if(lc($saneCounter) eq lc($cname)) { if(lc($saneInstance) eq lc($instance)) { $ctype = Win32::PerfLib::GetCounterType($cr->{'CounterType'}); print "\t\t* Found!\n"; print "\t\t* $cname->$instance is counter: $ctype\n"; } } } } } else { print "\t* Counter has multiple instances, ignoring because --instances not set\n"; } } foreach my $level4 (keys %{$pr->{'Objects'}->{$level2}->{'Counters'}}) { my $cr = $pr->{'Objects'}->{$level2}->{'Counters'}->{$level4}; my $saneCounter = $counter->{$cr->{'CounterNameTitleIndex'}}; print "\t\t\t* Debug: $saneCounter\n" if ($debug == 1); if(lc($saneCounter) eq lc($cname)) { $ctype = Win32::PerfLib::GetCounterType($cr->{'CounterType'}); print "\t\t* Found!\n"; print "\t\t* $cname is counter type: $ctype\n"; getFormula($ctype); } } } sub getFormula { my ($cdef) = @_; print < Syntax: $0 <-s|-o|-c> [-i|-d|-h] Accepts the following options: [opt] -s|--server Server name to fetch counters from (defaults to .> [req] -o|--object Object to fetch counters from (ie: System, Processor) [req] -c|--counter Name of counter (ie: % Processor Time) [opt] -i|--instance Instance name (ie: _Total) [opt] -d|--debug 0 = off, 1 = on [opt] -h|--help You are here. EOF exit 1; } cricket-1.0.5/util/pmlines.pl0100755000114300000000000001331707771711272015113 0ustar bertdwheel#!/usr/local/bin/perl # pmlines.pl # # 1998/08/04 # # Created by Carlos.Canau@EUnet.pt with cfgmaker from Tobias Oetiker # as skeleton # # returns: # #async # #digital # #isdn # #sync # #other # # 1998/10/30 # # Modified by Butch Kemper to allow multiple PortMasters # to be specified on the argument line and to output the totals. # # 1999/4/18 # # Modified by Butch Kemper to process PM2 systems and # to distingish between async and isdn ports. # # Changed name to PM2lines.pl # # 1999/4/19 # # Modified by Butch Kemper to process both PM2 and PM3 # systems. # # Changed name to PMlines.pl # use SNMP_Session; use BER; use Socket; use strict; # PMip is livingston.livingstonMib.livingstonInterfaces.livingstonSerial.livingstonSerialTable.livingstonSerialEntry.livingstonSerialIpAddress # PMty is livingston.livingstonMib.livingstonInterfaces.livingstonSerial.livingstonSerialTable.livingstonSerialEntry.livingstonSerialPhysType # TODO # - sum all the calls, connects, detects, renegotiates, and retrains # from livingstonModemTable. # # - think about using livingstonModemStatus instead of # livingstonSerialIpAddress to count lines in use (not portable to # PM2) (or livingstonSerialPortStatus?) %snmpget::OIDS = ('ifNumber' => '1.3.6.1.2.1.2.1.0', 'PMip' => '1.3.6.1.4.1.307.3.2.1.1.1.14', 'PMty' => '1.3.6.1.4.1.307.3.2.1.1.1.3', ); my($tot_isdn, $tot_modems, $args) = (0,0,0); my($tot_isdn, $tot_digitalmodems, $tot_asyncmodems, $tot_sync, $tot_other, $args) = (0,0,0,0,0,0); my($input_string, $PROGNAME, $interfaces)=""; ($PROGNAME = $0) =~ s/.*\///; diexit(0) if $#ARGV < 0; for ($args=0; $args < $#ARGV+1; $args++) { $input_string = $ARGV[$args]; my($community,$router) = split /\@/, $input_string; diexit(0) unless $community && $router; $interfaces = snmpget($router,$community,'ifNumber'); my @PMip = snmpgettable($router,$community,'PMip'); my @PMty = snmpgettable($router,$community,'PMty'); my ($i); for ($i=0; $i < $#PMip+1; $i++) { # # instead of livingstonSerialIpAddress we should use livingstonSerialPortStatus # and check for "established" (which might be applicable to terminal sessions) # if ($PMip[$i] ne "0.0.0.0") { if ($PMty[$i] == 2) { $tot_asyncmodems++; } elsif ($PMty[$i] == 3) { $tot_sync++; } elsif ($PMty[$i] == 5) { $tot_digitalmodems++; } elsif ($PMty[$i] == 4 || $PMty[$i] == 6 || $PMty[$i] == 7) { $tot_isdn++; } else { $tot_other++; } } } } printf "$tot_asyncmodems\n"; printf "$tot_digitalmodems\n"; printf "$tot_isdn\n"; printf "$tot_sync\n"; printf "$tot_other\n"; exit(0); sub diexit { die ("USAGE: $PROGNAME community\@portmaster [community\@portmaster]" . " \.\.\.\n" . " community = snmp read community string\n" . " portmaster = FQN of PortMaster\n"); } sub snmpget{ my($host,$community,@vars) = @_; my(@enoid, $var,$response, $bindings, $binding, $value, $inoid,$outoid, $upoid,$oid,@retvals); foreach $var (@vars) { die "Unknown SNMP var $var\n" unless $snmpget::OIDS{$var}; push @enoid, encode_oid((split /\./, $snmpget::OIDS{$var})); } srand(); my $session = SNMP_Session->open ($host , $community, 161); if ($session->get_request_response(@enoid)) { $response = $session->pdu_buffer; ($bindings) = $session->decode_get_response ($response); $session->close (); while ($bindings) { ($binding,$bindings) = decode_sequence ($bindings); ($oid,$value) = decode_by_template ($binding, "%O%@"); # WARNING: this makes getting values such as timeticks in raw form impossible my $tempo = pretty_print($value); $tempo=~s/\t/ /g; $tempo=~s/\n/ /g; $tempo=~s/^\s+//; $tempo=~s/\s+$//; push @retvals, $tempo; } return (@retvals); } else { die "No answer from $input_string. You may be using the wrong community\n"; } } sub snmpgettable{ my($host,$community,$var) = @_; my($next_oid,$enoid,$orig_oid, $response, $bindings, $binding, $value, $inoid,$outoid, $upoid,$oid,@table,$tempo); die "Unknown SNMP var $var\n" unless $snmpget::OIDS{$var}; $orig_oid = encode_oid(split /\./, $snmpget::OIDS{$var}); $enoid=$orig_oid; srand(); my $session = SNMP_Session->open ($host , $community, 161); for(;;) { if ($session->getnext_request_response(($enoid))) { $response = $session->pdu_buffer; ($bindings) = $session->decode_get_response ($response); ($binding,$bindings) = decode_sequence ($bindings); ($next_oid,$value) = decode_by_template ($binding, "%O%@"); # quit once we are outside the table last unless BER::encoded_oid_prefix_p($orig_oid,$next_oid); $tempo = pretty_print($value); #print "$var: '$tempo'\n"; $tempo=~s/\t/ /g; $tempo=~s/\n/ /g; $tempo=~s/^\s+//; $tempo=~s/\s+$//; push @table, $tempo; } else { die "No answer from $input_string\n"; } $enoid=$next_oid; } $session->close (); return (@table); } # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/util/relocate-perl0100755000114300000000000000266507047764267015604 0ustar bertdwheel#!/bin/sh # This little shell program searches the path for Perl, and once # it finds it, it substitutes the path into all the Perl executables # in the tree. It is intended to be run as part of the install process. isPerl5 () { $1 -e 'require 5;' > /dev/null 2>&1 } findPerl () { for i in `echo $PATH | tr ':' ' '` do for p in perl perl5 do if test -x $i/$p && isPerl5 $i/$p ; then echo $i/$p return 0 fi done done } process () { dir=$1 for i in $dir/* do if [ -d $i ]; then # recurse into directories (note: there's no guarantee # that $dir will be set right after this...) process $i elif [ -x $i ]; then echo " $i" $perl -i.bak -pe "s%^#!/usr/local/bin/perl%#!$perl%" $i chmod +x $i rm -f $i.bak fi done } if [ ! -f VERSION ]; then echo "Run this command from the Cricket distribution's root" echo "directory (the one with the VERSION file in it)." exit 1 fi perl=`findPerl` if [ ! -z "$perl" ]; then echo "Congratulations, you have Perl 5 installed as $perl" echo else echo "You don't seem to have Perl 5 in your path." echo "Either fix your path to include your perl, or" echo "visit http://www.perl.com, install Perl 5, and" echo "try again." exit 1 fi if [ "$perl" != "/usr/local/bin/perl" ]; then echo "Fixing executables to use your Perl binary." echo process . else echo "No need to process files, since your Perl is in a standard place." fi echo echo "Done." exit 0 cricket-1.0.5/util/rrd-dump0100755000114300000000000000355407454671377014577 0ustar bertdwheel#!/usr/local/bin/perl -w # -*- perl -*- # this is a little script to exercise RRD::File -- useful # when I was developing it, and possibly useful now too BEGIN { my $programdir = (($0 =~ m:^(.*/):)[0] || "./") . ".."; eval "require '$programdir/cricket-conf.pl'"; eval "require '/usr/local/etc/cricket-conf.pl'" unless $Common::global::gInstallRoot; $Common::global::gInstallRoot ||= $programdir; } use lib "$Common::global::gInstallRoot/lib"; use RRD::File; my($rrd) = new RRD::File ( -file => $ARGV[0] ); $rrd->open(); if ($rrd->loadHeader()) { my(@info) = ($rrd->ds_cnt(), $rrd->rra_cnt(), $rrd->pdp_step()); print "Header info: ", join(",", @info), "\n"; } else { print STDERR "Could not read RRD file: $RRD::File::gErr\n"; exit 1; }; print "\n"; foreach $i (0 .. $rrd->ds_cnt()-1) { print "DS $i:\t"; print join(",", %{$rrd->ds_def($i)}), "\n"; } print "\n"; foreach $i (0 .. $rrd->rra_cnt()-1) { print "RRA $i:\t"; print join(",", %{$rrd->rra_def($i)}), "\n"; } print "\n"; print "Live header:\n"; print "\tlast update time: ", $rrd->last_up(); print "\n\n"; foreach $i (0 .. $rrd->ds_cnt()-1) { print "PDP $i:\t"; print join(",", %{$rrd->pdp($i)}), "\n"; } print "\n"; print "CDP's:\n"; foreach $i (0 .. $rrd->rra_cnt()-1) { print "rra=$i\n"; foreach $j (0 .. $rrd->ds_cnt()-1) { print "\tds=$j:\t"; print join(",", %{$rrd->cdp($rrd->ds_cnt() * $i + $j)}), "\n"; } } print "\n"; print "RRA ptrs\n"; foreach $i (0 .. $rrd->rra_cnt()-1) { print " $i: ", $rrd->rra_ptr($i), "\n"; } print "\n"; print "Current values\n"; foreach $i (0 .. $rrd->ds_cnt()-1) { print " $i: ", $rrd->getDSCurrentValue($i), "\n"; } print "\n"; $rrd->close(); print "Metadata:\n"; $meta = $rrd->getMeta(); my($k); foreach $k (keys(%{$meta})) { print "\t$k: $meta->{$k}\n"; # for testing setMeta # if ($k eq "last-inst") { # $meta->{$k}++; # } } #$rrd->setMeta($meta); cricket-1.0.5/util/rrd-tune0100755000114300000000000001715207757471020014571 0ustar bertdwheel#!/usr/local/bin/perl -w # -*- perl -*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. BEGIN { my $programdir = (($0 =~ m:^(.*/):)[0] || "./") . ".."; eval "require '$programdir/cricket-conf.pl'"; eval "require '/usr/local/etc/cricket-conf.pl'" unless $Common::global::gInstallRoot; $Common::global::gInstallRoot ||= $programdir; } use lib "$Common::global::gInstallRoot/lib"; use RRDs 1.000101; use ConfigTree::Cache; use Common::HandleTarget; use Common::Map; use Common::Log; use Getopt::Long; my($VERSION) = "rrd-tune 1.1"; # Parse common options & script specific arguments my($clear) = localOptions(); $Common::global::gCT = new ConfigTree::Cache; $gCT = $Common::global::gCT; $gCT->Base($Common::global::gConfigRoot); $gCT->Warn(\&Warn); if (! $gCT->init()) { Die("Failed to open compiled config tree from " . "$Common::global::gConfigRoot/config.db: $!"); } my($recomp, $why) = $gCT->needsRecompile(); if ($recomp) { Die("Config tree needs to be recompiled: $why"); } # Remaining arguments are the subtrees my (@root) = (@ARGV) ? @ARGV : "/"; # foreach subtree to do # find the base node of that subtree # foreach leaf node of this subtree # process it my($subtree); foreach $subtree (@root) { if ($gCT->nodeExists($subtree)) { $gCT->visitLeafs($subtree, \&handleTarget, \&handleTargetInstance, \&localHandleTargetInstance); } else { Warn("Unknown subtree $subtree."); } } exit; sub localHandleTargetInstance { my($name, $target) = @_; my($tname) = $target->{'auto-target-name'}; # don't try to tune multitargets if ($target->{'targets'}) { return; } my($ttype) = $target->{'target-type'}; if (! $ttype) { Warn("Skipping RRD tune for $tname; no target type."); return; } my($ttRef) = $main::gCT->configHash($name, 'targettype', lc($ttype), $target); if (! defined($ttRef)) { Warn("Skipping RRD tune for $tname; unknown target type."); return; } my($dslist) = $ttRef->{'ds'}; if (! defined($dslist)) { Warn("Skipping RRD tune for $tname; no DS's for target-type $ttype."); return; } # Does the RRD file exist my($rrd) = $target->{'rrd-datafile'}; if (! $rrd) { Warn("Skipping RRD tune for $tname; could not find " . " rrd-datafile."); return; } # Run the tuning agent to ajust RRD max and min values for each DS of the RRD tuneRRD($name, $target, $ttype, $ttRef, $dslist, $rrd); # Remove orphaned alarms in the meta data file associated with a valid RRD cleanMeta($name, $target); return 1; } sub cleanMeta { my($name, $target) = @_; my($tname) = $target->{'auto-target-name'}; return if ($tname ne "chassis"); ### meta file processing that contains active alarms my($rrd) = new RRD::File( -file=>$target->{'rrd-datafile'} ); my($refMeta) = $rrd->getMeta(); ### create a space delimited string with the monitor thresholds my ($monitors); if (defined $target->{'monitor-thresholds'}) { $monitors = ' ' . $target->{'monitor-thresholds'}; $monitors =~ s/[\s,]+/ /g; } foreach my $key (keys %{$refMeta}) { Info (" Clear this alarm?: $key data: $refMeta->{$key}"); next if ($key =~ /last-inst/); if ($monitors) { my $found = ($monitors =~ / $key /) ? 1 : 0; delete($refMeta->{$key}) if (!$found || $clear); Info (" Clearing alarm: $key") if (!$found || $clear); } else { delete($refMeta->{$key}); Info (" Clearing alarm: $key"); } } $rrd->setMeta($refMeta); return 1; } sub tuneRRD { my($name, $target, $ttype, $ttRef, $dslist, $rrd) = @_; my($tname) = $target->{'auto-target-name'}; # run rrd tune on the RRD my(@arg); my($dsnum) = 0; my($dsname); foreach $dsname (split(/\s*,\s*/, $dslist)) { my($ds) = $main::gCT->configHash($name, 'datasource', lc($dsname), $target); if (! defined($ds)) { Warn("Unknown datasource: $dsname"); next; } my($dst) = $ds->{'rrd-ds-type'}; $dst = "GAUGE" unless (defined($dst)); $dst = uc($dst); # Never tune COMPUTE data sources; it will corrupt the header. # If the user really wants to replace the COMPUTE data source with # another type, they can do it manually. if ($dst eq 'COMPUTE') { $dsnum++; Info("Skipping RRD tune for COMPUTE data source $dsname"); next; } my($hb) = $ds->{'rrd-heartbeat'}; $hb = $target->{'rrd-heartbeat'} if (defined($target->{'rrd-heartbeat'})); $hb = 1800 unless (defined($hb)); my($min) = $ds->{'rrd-min'}; $min = $target->{'rrd-min'} if (defined($target->{'rrd-min'})); $min = 'U' unless (defined($min)); my($max) = $ds->{'rrd-max'}; $max = $target->{'rrd-max'} if (defined($target->{'rrd-max'})); $max = 'U' unless (defined($max)); push(@arg, '-d', "ds$dsnum:$dst", '-h', "ds$dsnum:$hb", '-i', "ds$dsnum:$min", '-a', "ds$dsnum:$max"); $dsnum++; } Info("Tuning $tname"); Debug("RRDs::tune $rrd ", join(" ", @arg)); RRDs::tune $rrd, @arg; my($err) = RRDs::error(); if ($error) { Warn("Unable to tune $rrd: $error\n"); return; } return 1; } sub localOptions { my ($logLevel, $logFormat, $base, $clear, $usage); # default to 'info' unless there's a environment variable # or a commandline arg $logLevel = $Common::global::gLogLevel if $Common::global::gLogLevel; $logLevel = $ENV{'CRICKET_LOG_LEVEL'} if $ENV{'CRICKET_LOG_LEVEL'}; $logLevel ||= "info"; # default to 'standard' unless there's a environment variable $logFormat = $Common::global::gLogFormat if $Common::global::gLogFormat; $logFormat = $ENV{'CRICKET_LOG_FORMAT'} if $ENV{'CRICKET_LOG_FORMAT'}; $logFormat ||= "standard"; GetOptions( "loglevel:s" => \$logLevel, "logformat:s" => \$logFormat, "base:s" => \$base, "clear" => \$clear, "help|h" => \$usage); usage() if ($usage); $Common::global::gConfigRoot = $base if $base; Common::Log::setLevel($logLevel); Common::Log::setFormat($logFormat); return ($clear); } sub usage { #' print STDERR < Default Actions: - Cleanout orphaned monitor thresholds alarms - Ajust min and max RRD database values --clear - Remove all monitor threshold alarms - Select a protion of the config-tree --loglevel - Select the logging level --logformat - Select the logging format --help | -h - Display the current help file EXAMPLE: $0 EXAMPLE: $0 /cisco-routers/siteA/routerA EXAMPLE: $0 --clear /cisco-routers EXAMPLE: $0 --clear EOD #' exit(1); } cricket-1.0.5/util/rrdTuneAberrant.cgi0100755000114300000000000001316407635415061016671 0ustar bertdwheel#!/usr/local/bin/perl -wT # -*- perl -*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. BEGIN { # this script lives in the util subdirectory my $programdir = (($0 =~ m:^(.*/)util:)[0] || "./") . "."; eval "require '$programdir/cricket-conf.pl'"; eval "require '$programdir/../cricket-conf.pl'" unless $Common::global::gInstallRoot; $Common::global::gInstallRoot ||= $programdir; } use lib "$Common::global::gInstallRoot/lib"; use CGI qw(carpout fatalsToBrowser); use RRDs 1.000101; use ConfigTree::Cache; use Common::Version; use Common::global; use Common::HandleTarget; use Common::Util; use Common::Log; $log = 'debug'; Common::Log::setLevel($log); $gQ = new CGI; fixHome($gQ); $Common::global::gCT = new ConfigTree::Cache; $gCT = $Common::global::gCT; $gCT->Base($Common::global::gConfigRoot); $gCT->Warn(\&Warn); print $gQ -> header('text/plain'); if (! $gCT->init()) { print "Failed to open compiled config tree from " . "$Common::global::gConfigRoot/config.db: $!"; exit(0); } my($recomp, $why) = $gCT->needsRecompile(); if ($recomp) { print "Config tree needs to be recompiled: $why"; exit; } # validate target name my($name) = $gQ->param("target"); my($targetRef) = $gCT->configHash($name,'target'); if (!defined($targetRef)) { print "Specified target missing or invalid"; exit; } ConfigTree::Cache::addAutoVariables($name, $targetRef, $Common::global::gConfigRoot); my($ttRef) = $gCT->configHash($name, 'targettype', lc($targetRef->{'target-type'})); if (! defined($ttRef)) { print "Invalid or missing target-type for $name"; exit; } # validate data source my($ds) = $gQ -> param('ds'); if (!defined($ds)) { print "Missing data source name"; exit; } my($dslist) = $ttRef->{'ds'}; my($dsnum) = 0; my($bFound) = 0; foreach $dsname (split(/\s*,\s*/, $dslist)) { if ($dsname eq $ds) { $bFound = 1; last; } $dsnum++ } if (!$bFound) { print "Specified data source $ds does not exist for target $name"; exit; } my($rrd) = $targetRef->{'rrd-datafile'}; # is this a vector-instance target? # borrowed from HandleTarget my(@inst); if (defined($targetRef->{'inst'})) { my($inst) = $targetRef->{'inst'}; $inst = ConfigTree::Cache::expandString($inst, $targetRef, \&Warn); # assume $inst is something like (1..8) or (50,51,52) $inst =~ s/^\s*\(//; $inst =~ s/\)\s*$//; if ($inst =~ /([0-9]+)\.\.([0-9]+)/) { if ($2 > $1) { my ($start,$end) = ($1, $2); for ( ;$start <= $end; ++$start) { push @inst, $start; } } } else { @inst = split(/\s*,\s*/,$inst); } } else { @inst = (); } if (scalar(@inst) > 1) { if (!defined($gQ->param('inst'))) { print "Target is a vector instance, but no instance specified"; exit; } # is the inst valid? $bFound = 0; foreach $inst (@inst) { if ($inst eq $gQ->param('inst')) { $bFound = 1; last; } } if (!$bFound) { print "Instance specified " . $gQ->param('inst') . " is not valid for target $name"; exit; } # because inst appears in rrd-datafile, we need to remap inst to a scalar my($inst_save) = $targetRef->{'inst'}; $targetRef->{'inst'} = $gQ->param('inst'); $rrd = ConfigTree::Cache::expandString($rrd, $targetRef, \&Warn); # not strictly necessary because at the moment this value won't be used # down stream... however, that may change in the future $targetRef->{'inst'} = $inst_save; } else { $rrd = ConfigTree::Cache::expandString($rrd, $targetRef, \&Warn); } # at this point, we have: # a rrd data file pathname my @arg; if ($Common::global::gLongDSName) { @arg = ('--aberrant-reset',"$dsname"); } else { # a data source number (to compute the Cricket ds# name) @arg = ('--aberrant-reset',"ds$dsnum"); } RRDs::tune $rrd, @arg; my($err) = RRDs::error(); if ($err) { print "Failed:\ntune $rrd " . join(" ",@arg); print "\nRRDtool: $err\n"; } else { print "Success:\ntune $rrd " . join(" ",@arg) . "\n"; } 1; # borrowed from grapher.cgi sub fixHome { return if (defined($Common::global::gCricketHome) && $Common::global::gCricketHome =~ /\//); my($sname) = $gQ->script_name(); if ($sname =~ /\/~([^\/]*)\//) { my($username) = $1; my($home) = (getpwnam($username))[7]; if ($home) { $Common::global::gCricketHome = $home; return; } else { Info("Could not find a home directory for user $username." . "gCricketHome is probably not set right."); } } else { Info("Could not find a username in SCRIPT_NAME. " . "gCricketHome is probably not set right."); } $Common::global::gCricketHome ||= $Common::global::gInstallRoot . "/.."; } cricket-1.0.5/util/run-subtree0100755000114300000000000000464407560266776015322 0ustar bertdwheel#!/usr/local/bin/perl -w # -*- perl -*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # # run a cricket subtree and send any output to the provided email addresses # use strict; use Getopt::Long; use Net::SMTP; use Net::DNS; my $result = ""; my @output = (); my $doSubtree; # Define your default email address here if you don't want to use -m my $doMail = 'smrtg@corp.webtv.net'; # Define your SMTP host here my $mx = 'smtplocal-2001-2.private.lawson.webtv.net'; $result = GetOptions( "mail|m=s" => \$doMail, "subtree|s=s" => \$doSubtree, ); if (!$result) { die "Error handling options.\n"; } if (!defined($doSubtree)) { die "Must provide a subtree to run.\n"; } @output = `perl c:/crickethome/cricket/collect-subtrees $doSubtree 2>&1`; if ($#output > 0) { my $res = Net::DNS::Resolver->new; # Define nameservers here if you don't have a libresolv (like on # Windows systems) #$res->nameservers('127.0.0.1'); my ($eUser, $eDomain) = split(/\@/, $doMail); my @mx = mx($res, $eDomain); my $subject = "output from subtree " . $doSubtree; # Comment the $mx[0]->{'exchange'} line and uncomment the ($mx) line # if you don't want DNS to figure out the exchanger. It may not be # able to talk to the DNS-determined relay if you're behind a firewall. #my $smtp = Net::SMTP->new($mx[0]->{'exchange'}); my $smtp = Net::SMTP->new($mx); $smtp->mail($doMail); $smtp->to($doMail); $smtp->data(); $smtp->datasend("Subject: $subject"); $smtp->datasend(@output); $smtp->dataend(); $smtp->quit; } cricket-1.0.5/util/run-subtree.old0100755000114300000000000000343707560267021016056 0ustar bertdwheel#!/usr/local/bin/perl -w # -*- perl -*- # Cricket: a configuration, polling and data display wrapper for RRD files # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # # run a cricket subtree and send any output to the provided email addresses # use strict; use Getopt::Long; use Net::SMTP; use Net::DNS; my $result = ""; my @output = (); my $doMail = 'smrtg@corp.webtv.net'; my $doSubtree; $result = GetOptions( "mail|m=s" => \$doMail, "subtree|s=s" => \$doSubtree, ); if (!$result) { die "Error handling options.\n"; } if (!defined($doSubtree)) { die "Must provide a subtree to run.\n"; } @output = `$ENV{"HOME"}/cricket/collect-subtrees $doSubtree 2>&1`; if ($#output > 0) { my $res = Net::DNS::Resolver->new; my ($eUser, $eDomain) = split(/\@/, $doMail); my @mx = mx($res, $eDomain); my $subject = "output from subtree " . $doSubtree; my $smtp = Net::SMTP->new($mx[0]->{'exchange'}, Debug => 1); $smtp->mail($doMail); $smtp->to($doMail); $smtp->data(); $smtp->datasend("Subject: $subject"); $smtp->datasend(@output); $smtp->dataend(); $smtp->quit; } cricket-1.0.5/util/systemPerfConf.pl0100755000114300000000000002326007777405654016423 0ustar bertdwheel#!/usr/local/bin/perl -w # Host config file generator for Cricket, based on work by # James Moore and Grimshaw Stuart # # Hacked beyond recognition by Bert Driehuis to update the MIBs used and # remove some dependancy on the presence of MIB files. # Michael Han added the handling of network interface and sanitized the # SNMP interface. # # Options are shown by running this script with the --help option. # Use like you would use listInterfaces, i.e. make sure these directories # and files exist: # cricket-config/Defaults [from sample-config/Defaults] # cricket-config/systemperf # cricket-config/systemperf/Defaults [from sample-config/systemperf/Defaults] # cricket-config/systemperf/fileserver.yourcompany.com # cricket-config/systemperf/mailserver.yourcompany.com # and run this script at regular intervals (say, daily at midnight), like # % systemPerfConf.pl --host fileserver.yourcompany.com \ # > cricket-config/systemperf/fileserver.yourcompany.com/Targets # % systemPerfConf.pl --host mailserver.yourcompany.com \ # > cricket-config/systemperf/mailserver.yourcompany.com/Targets BEGIN { my $programdir = (($0 =~ m:^(.*/):)[0] || "./") . ".."; eval "require '$programdir/cricket-conf.pl'"; eval "require '/usr/local/etc/cricket-conf.pl'" unless $Common::global::gInstallRoot; $Common::global::gInstallRoot ||= $programdir; } use lib "$Common::global::gInstallRoot/lib"; use Getopt::Long; use snmpUtils; # Option values my $help = 0; my $community = "public"; my $auto = 0; my $host; my $include; my $skip; # OID dictionary my %oid = ("sysDescr" => "1.3.6.1.2.1.1.1", "ifDescr" => "1.3.6.1.2.1.2.2.1.2", "ssCpuRawUser" => "1.3.6.1.4.1.2021.11.50.0", "diskIODevice" => "1.3.6.1.4.1.2021.13.15.1.1.2", "hrStorageFixedDisk" => "1.3.6.1.2.1.25.2.1.4", "hrStorageType" => "1.3.6.1.2.1.25.2.3.1.2", "hrStorageDescr" => "1.3.6.1.2.1.25.2.3.1.3", "hrStorageAllocationUnits" => "1.3.6.1.2.1.25.2.3.1.4", "hrStorageSize" => "1.3.6.1.2.1.25.2.3.1.5", "hrSystemNumUsers" => "1.3.6.1.2.1.25.1.5.0"); # Options accepted: GetOptions('auto'=>\$auto, 'community:s'=>\$community, 'help'=>\$help, 'host:s'=>\$host, 'include:s'=>\$include, 'skip:s'=>\$skip); print_help() if $help; print_help("--host is a required option") if !$host; print_help("Specify either --auto or --include=...") if !$auto && !$include; my $unavailable_ok = 1 if $auto; my %include = ("system" => 1, "storage" => 1, "diskio" => 1, "cpu" => 1, "interface" => 1) if $auto; if ($include) { foreach my $what (split(/\s*,\s*/, $include)) { $include{$what} = 1; } } my @skip = split(/\s*,\s*/, $skip) if $skip; my $snmp = "$community\@$host"; my $system_objectId = snmpUtils::get($snmp, "$oid{sysDescr}.0"); die "Can't contact $host" unless $system_objectId; my $order = 999; print template_header($host, $community); print get_systemtable($snmp) if defined($include{"system"}); print get_cputable($snmp) if defined($include{"cpu"}); print get_iftable($snmp) if defined($include{"interface"}); print get_disktable($snmp) if defined($include{"storage"}); print get_diskiotable($snmp) if defined($include{"diskio"}); sub print_help { my $string = shift; print STDERR "$string\n\n" if $string; print STDERR <<"EOF"; usage: $0 --host [--community ] [options] --host: name of host to be monitored --community: community string for host (Default: public) --help: prints this help --auto: try to include all possible monitorable items --include=... include only specific monitorable items (comma separated list): system system users and processes cpu CPU usage storage disk space diskio disk I/O stats --skip=... comma separated list of regular expressions to skip devices you don't want monitored An example: $0 --host mailserver --auto --skip='diskio_[mf]d,disk_dos' Autodetect all MIBs on mailserver, but skip diskio stats for md and fd devices, and skip any dos partitions $0 --host fileserver --include='cpu,storage' Only collect CPU usage and disk space on host "fileserver". EOF exit(1); } sub template_header { my ($host, $community) = @_; my $tmpl = <<"EOF"; target --default-- server = $host snmp-community = $community EOF return $tmpl; } sub template_hr_sys { my $tmpl = <<"EOF"; target hr_sys target-type = hr_System inst = 0 short-desc = \"# of system processes and users\" order = $order EOF $order--; return $tmpl; } sub template_ucd_sys { my $tmpl = <<"EOF"; target ucd_sys target-type = ucd_System short-desc = \"CPU, Memory, and Load\" order = $order EOF $order--; return $tmpl; } sub template_hr_storage { my ($target, $index, $path, $size, $blocksize) = @_; return "" if isSkipTarget($target); my $tmpl = <<"EOF"; target $target target-type = hr_Storage inst = map(hr-storage-name) hr-storage-name = "$path" short-desc = \"Bytes used on $path\" max-size = $size min-size = $blocksize storage = $target units = $blocksize,* order = $order EOF $order--; return $tmpl; } sub template_interface { my ($target) = @_; return "" if isSkipTarget("if_$target"); my $tmpl = <<"EOF"; target if_$target target-type = standard-interface inst = map(interface-name) interface-name= "$target" short-desc = \"network activity on $target\" order = $order EOF $order--; return $tmpl; } sub template_ucd_diskio { my ($target) = @_; return "" if isSkipTarget("diskio_$target"); my $tmpl = <<"EOF"; target diskio_$target target-type = ucd_diskio inst = map(ucd-diskio-device) ucd-diskio-device = "$target" short-desc = \"disk I/O on $target\" order = $order EOF $order--; return $tmpl; } sub get_cputable { my $snmp = shift; my $rawcpu = snmpUtils::get($snmp, $oid{"ssCpuRawUser"}); print STDERR "Could not fetch ucd_rawcpu table\n" unless $auto; return template_ucd_sys(); } sub get_iftable { my $snmp = shift; my $tmpl = ""; my $junk; my @interfaces = snmpUtils::walk($snmp, $oid{"ifDescr"}); map { ($junk, $_)= split /:/, $_ } @interfaces; foreach (@interfaces) { $tmpl .= template_interface($_); } return $tmpl; } sub get_diskiotable { my $snmp = shift; my $tmpl = ""; my $junk; my @disknames = snmpUtils::walk($snmp, $oid{"diskIODevice"}); map { ($junk, $_)= split /:/, $_ } @disknames; foreach (@disknames) { $tmpl .= template_ucd_diskio($_); } return $tmpl; } sub get_disktable { my $snmp = shift; my $tmpl = ""; my ($junk, $key, $val, @list); @list = snmpUtils::walk($snmp, $oid{"hrStorageDescr"}); if ($#list < 0) { print STDERR "Could not fetch hr_storage table\n" unless $auto; return ""; } map { ($key, $val) = split /:/, $_; $disknames{$key} = $val; } @list; @list = snmpUtils::walk($snmp, $oid{"hrStorageType"}); map { ($key, $val) = split /:/, $_; $disktypes{$key} = $val; } @list; @list = snmpUtils::walk($snmp, $oid{"hrStorageAllocationUnits"}); map { ($key, $val) = split /:/, $_; $diskunits{$key} = $val; } @list; @list = snmpUtils::walk($snmp, $oid{"hrStorageSize"}); map { ($key, $val) = split /:/, $_; $disksizes{$key} = $val; } @list; my (%diskbytes, %disktargets); foreach my $idx (sort { $a<=>$b } keys %disknames) { my $targetname = $disknames{$idx}; if ( ($disktypes{$idx} ne $oid{"hrStorageFixedDisk"}) || ($idx > 100) || ($targetname =~ /\/proc\b/)) { delete $disknames{$idx}; next; } $targetname =~ s/^\///; $targetname = "root" if $targetname eq ""; $targetname =~ s/\//_/g; $disktargets{$idx} = "disk_" . $targetname; $diskbytes{$idx} = $diskunits{$idx} * $disksizes{$idx}; } my $saved_order = $order--; my $saved_order2 = $order--; my @alltargets = (); foreach $idx (sort { $a<=>$b } keys %disknames) { $tmpl .= template_hr_storage($disktargets{$idx}, $idx, $disknames{$idx}, $diskbytes{$idx}, $diskunits{$idx}); push @alltargets, $disktargets{$idx}; } my $alltargets = join(';', @alltargets); my $mtargets_tmpl =<<"EOF"; target disks_all target-type = hr_Storage mtargets = $alltargets short-desc = \"Disk usage (all disks)\" order = $saved_order #target disks_pct # target-type = hr_StoragePct # mtargets = $alltargets # short-desc = \"Disk usage percentage (all disks)\" # order = $saved_order2 EOF return $mtargets_tmpl . $tmpl; } sub get_systemtable { my $snmp = shift; my $system_numusers = snmpUtils::get($snmp, $oid{"hrSystemNumUsers"}); if (!defined($system_numusers)) { return "" if $unavailable_ok; die "Cannot get number of users logged on"; } return template_hr_sys(); } sub isSkipTarget { my $target = shift; foreach my $skip (@skip) { return 1 if ($target =~ /^$skip/); } return 0; } # Local Variables: # mode: perl # indent-tabs-mode: nil # tab-width: 4 # perl-indent-level: 4 # End: cricket-1.0.5/util/test-url0100755000114300000000000000324107047764267014614 0ustar bertdwheel#!/usr/local/bin/perl -w # -*- perl -*- # test-url: a simple script to see how fast a webserver is # # Copyright (C) 1998 Jeff R. Allen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. use LWP::UserAgent; use Time::HiRes qw(gettimeofday); my $datalen = 0; $ua = new LWP::UserAgent; $ua->agent("test-url/1.0 " . $ua->agent); $ua->timeout(30); die("Missing URL on command line.\n") unless (defined($ARGV[0])); $req = new HTTP::Request('GET', $ARGV[0]); die("Could not make an HTTP request.\n") unless (defined($req)); $start = now(); my($resp) = $ua->request($req, \&callback, 4096); $stop = now(); if (! $first) { $first = $stop; } $dFirst = $first - $start; $dTotal = $stop - $start; $dFirst = "U" if (! $datalen); $dTotal = "U" if (! $datalen); print "$dTotal\n"; print "$dFirst\n"; sub callback { my($data, $resp, $proto) = @_; if (! defined($first)) { $first = now(); } $datalen += length($data); } sub now { my(@t) = gettimeofday(); return $t[0] + ($t[1] / 1000000.0); } cricket-1.0.5/util/testwbem.pl0100755000114300000000000000140207770173524015267 0ustar bertdwheel use lib "../lib"; use strict; use Common::Log; use wbem; Common::Log::setLevel('debug'); # For 0,1, & 4 the consecutive colons (::) indicate poll the localhost # 2 & 3 poll a remote host, substitute an appropriate hostname for my @temp = ( '0::Root/CIMV2:WIN32_PerfRawData_PerfDisk_PhysicalDisk:DiskReadsPersec:Name=\'_Total\'', '1::Root/CIMV2:CIM_Processor:LoadPercentage:DeviceID=\'CPU0\'', '2::Root/CIMV2:WIN32_PerfRawData_PerfDisk_PhysicalDisk:DiskReadsPersec:Name=\'_Total\'', '3::Root/CIMV2:CIM_Processor:LoadPercentage:DeviceID=\'CPU0\'', '4::Root/CIMV2:WIN32_PerfRawData_PerfDisk_PhysicalDisk:DiskWritesPersec:Name=\'_Total\''); my @results = &{$main::gDSFetch{'wbem'}}(\@temp); print join("\n",@results) . "\n"; cricket-1.0.5/util/usrModemUsage0100755000114300000000000000374407454671377015626 0ustar bertdwheel#!/usr/local/bin/perl -w # -*- perl -*- # usrModemUsage # # Copyright (C) 1998 Jeff Jensen and WebTV Networks, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # Program to return usage information from a US Robotics # modem chassis. BEGIN { my $programdir = (($0 =~ m:^(.*/):)[0] || "./") . ".."; eval "require '$programdir/cricket-conf.pl'"; eval "require '/usr/local/etc/cricket-conf.pl'" unless $Common::global::gInstallRoot; $Common::global::gInstallRoot ||= $programdir; } use lib "$Common::global::gInstallRoot/lib"; use strict; use Common::Log; use snmpUtils; # # Take the hostname and community as arguments # my $hostname = $ARGV[0]; my $community = "public"; $community = $ARGV[1] if (defined($ARGV[1]));; die("usage: $0 host [community-string]\n") unless (defined($hostname) && defined($community)); my($modemTableOid) = '.1.3.6.1.4.1.429.1.6.9.1.1.2'; my($inUse) = 0; my($neg) = 0; my(@res) = snmpUtils::walk("$community\@$hostname", $modemTableOid); # did we get nothing? if ($#res+1 == 0) { print "U\n"; print "U\n"; exit; } my($row); foreach $row (@res) { my($inst, $value) = split(':', $row, 2); # these state numbers are taken from the USR mibs. if ($value == 8) { $inUse++; } elsif ($value == 6) { $neg++; } else { # unknown state... ignore it } } print "$inUse\n"; print "$neg\n";