htm 0.0.10 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.dictate.toml +46 -0
- data/.envrc +2 -0
- data/CHANGELOG.md +86 -3
- data/README.md +86 -7
- data/Rakefile +14 -2
- data/bin/htm_mcp.rb +621 -0
- data/config/database.yml +20 -13
- data/db/migrate/00010_add_soft_delete_to_associations.rb +29 -0
- data/db/migrate/00011_add_performance_indexes.rb +21 -0
- data/db/migrate/00012_add_tags_trigram_index.rb +18 -0
- data/db/migrate/00013_enable_lz4_compression.rb +43 -0
- data/db/schema.sql +49 -92
- data/docs/api/index.md +1 -1
- data/docs/api/yard/HTM.md +2 -4
- data/docs/architecture/index.md +1 -1
- data/docs/development/index.md +1 -1
- data/docs/getting-started/index.md +1 -1
- data/docs/guides/index.md +1 -1
- data/docs/images/telemetry-architecture.svg +153 -0
- data/docs/telemetry.md +391 -0
- data/examples/README.md +171 -1
- data/examples/cli_app/README.md +1 -1
- data/examples/cli_app/htm_cli.rb +1 -1
- data/examples/mcp_client.rb +529 -0
- data/examples/sinatra_app/app.rb +1 -1
- data/examples/telemetry/README.md +147 -0
- data/examples/telemetry/SETUP_README.md +169 -0
- data/examples/telemetry/demo.rb +498 -0
- data/examples/telemetry/grafana/dashboards/htm-metrics.json +457 -0
- data/lib/htm/configuration.rb +261 -70
- data/lib/htm/database.rb +46 -22
- data/lib/htm/embedding_service.rb +24 -14
- data/lib/htm/errors.rb +15 -1
- data/lib/htm/jobs/generate_embedding_job.rb +19 -0
- data/lib/htm/jobs/generate_propositions_job.rb +103 -0
- data/lib/htm/jobs/generate_tags_job.rb +24 -0
- data/lib/htm/loaders/markdown_chunker.rb +79 -0
- data/lib/htm/loaders/markdown_loader.rb +41 -15
- data/lib/htm/long_term_memory/fulltext_search.rb +138 -0
- data/lib/htm/long_term_memory/hybrid_search.rb +324 -0
- data/lib/htm/long_term_memory/node_operations.rb +209 -0
- data/lib/htm/long_term_memory/relevance_scorer.rb +355 -0
- data/lib/htm/long_term_memory/robot_operations.rb +34 -0
- data/lib/htm/long_term_memory/tag_operations.rb +428 -0
- data/lib/htm/long_term_memory/vector_search.rb +109 -0
- data/lib/htm/long_term_memory.rb +51 -1153
- data/lib/htm/models/node.rb +35 -2
- data/lib/htm/models/node_tag.rb +31 -0
- data/lib/htm/models/robot_node.rb +31 -0
- data/lib/htm/models/tag.rb +44 -0
- data/lib/htm/proposition_service.rb +169 -0
- data/lib/htm/query_cache.rb +214 -0
- data/lib/htm/sql_builder.rb +178 -0
- data/lib/htm/tag_service.rb +16 -6
- data/lib/htm/tasks.rb +8 -2
- data/lib/htm/telemetry.rb +224 -0
- data/lib/htm/version.rb +1 -1
- data/lib/htm.rb +64 -3
- data/lib/tasks/doc.rake +1 -1
- data/lib/tasks/htm.rake +259 -13
- data/mkdocs.yml +96 -96
- metadata +75 -18
- data/.aigcm_msg +0 -1
- data/.claude/settings.local.json +0 -92
- data/CLAUDE.md +0 -603
- data/examples/cli_app/temp.log +0 -93
- data/lib/htm/loaders/paragraph_chunker.rb +0 -112
- data/notes/ARCHITECTURE_REVIEW.md +0 -1167
- data/notes/IMPLEMENTATION_SUMMARY.md +0 -606
- data/notes/MULTI_FRAMEWORK_IMPLEMENTATION.md +0 -451
- data/notes/next_steps.md +0 -100
- data/notes/plan.md +0 -627
- data/notes/tag_ontology_enhancement_ideas.md +0 -222
- data/notes/timescaledb_removal_summary.md +0 -200
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# HTM Telemetry Demo Setup
|
|
2
|
+
|
|
3
|
+
This guide walks you through setting up Prometheus and Grafana to visualize HTM metrics.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
### 1. Install Prometheus and Grafana via Homebrew
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
brew install prometheus grafana
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### 2. Install Required Ruby Gems
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
gem install prometheus-client webrick
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Starting the Services
|
|
20
|
+
|
|
21
|
+
### Start Prometheus and Grafana
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
brew services start prometheus
|
|
25
|
+
brew services start grafana
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Verify they're running:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
brew services list | grep -E 'prometheus|grafana'
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Both should show `started` status.
|
|
35
|
+
|
|
36
|
+
## Configure Grafana
|
|
37
|
+
|
|
38
|
+
### 1. Access Grafana
|
|
39
|
+
|
|
40
|
+
Open http://localhost:3000 in your browser.
|
|
41
|
+
|
|
42
|
+
**Default credentials:**
|
|
43
|
+
- Username: `admin`
|
|
44
|
+
- Password: `admin`
|
|
45
|
+
|
|
46
|
+
You'll be prompted to change the password on first login.
|
|
47
|
+
|
|
48
|
+
### 2. Add Prometheus Data Source
|
|
49
|
+
|
|
50
|
+
1. Go to **Connections** → **Data sources**
|
|
51
|
+
2. Click **Add data source**
|
|
52
|
+
3. Select **Prometheus**
|
|
53
|
+
4. Enter URL: `http://localhost:9090`
|
|
54
|
+
5. Click **Save & test**
|
|
55
|
+
|
|
56
|
+
You should see "Successfully queried the Prometheus API."
|
|
57
|
+
|
|
58
|
+
### 3. Import the HTM Dashboard
|
|
59
|
+
|
|
60
|
+
1. Go to **Dashboards** → **Import**
|
|
61
|
+
2. Click **Upload JSON file**
|
|
62
|
+
3. Select: `examples/telemetry/grafana/dashboards/htm-metrics.json`
|
|
63
|
+
4. Select your Prometheus datasource from the dropdown
|
|
64
|
+
5. Click **Import**
|
|
65
|
+
|
|
66
|
+
## Running the Demo
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
cd examples/telemetry
|
|
70
|
+
./demo.rb
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The demo will:
|
|
74
|
+
- Verify Prometheus and Grafana are installed and running
|
|
75
|
+
- Configure Prometheus to scrape metrics from port 9394
|
|
76
|
+
- Clean up any previous demo data
|
|
77
|
+
- Open Grafana in your browser
|
|
78
|
+
- Run HTM operations in a loop, exporting metrics
|
|
79
|
+
|
|
80
|
+
## Viewing Metrics
|
|
81
|
+
|
|
82
|
+
Once the demo is running, view the dashboard at:
|
|
83
|
+
|
|
84
|
+
http://localhost:3000/d/htm-metrics/htm-metrics
|
|
85
|
+
|
|
86
|
+
The dashboard shows:
|
|
87
|
+
- **Total Successful Jobs** - Embedding and tag job completions
|
|
88
|
+
- **Total Failed Jobs** - Job failures
|
|
89
|
+
- **Cache Hit Rate** - Query cache effectiveness
|
|
90
|
+
- **LLM Job Latency (p95)** - Embedding and tag generation times
|
|
91
|
+
- **Search Latency by Strategy** - Vector, fulltext, and hybrid search times
|
|
92
|
+
- **Jobs per Minute** - Throughput by job type
|
|
93
|
+
- **Cache Operations** - Hit/miss rate over time
|
|
94
|
+
|
|
95
|
+
## Troubleshooting
|
|
96
|
+
|
|
97
|
+
### Prometheus won't start
|
|
98
|
+
|
|
99
|
+
Check the error log:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
cat /opt/homebrew/var/log/prometheus.err.log
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Common issue: YAML syntax error in config. Verify the config:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
cat /opt/homebrew/etc/prometheus.yml
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The config should look like:
|
|
112
|
+
|
|
113
|
+
```yaml
|
|
114
|
+
global:
|
|
115
|
+
scrape_interval: 15s
|
|
116
|
+
|
|
117
|
+
scrape_configs:
|
|
118
|
+
- job_name: "prometheus"
|
|
119
|
+
static_configs:
|
|
120
|
+
- targets: ["localhost:9090"]
|
|
121
|
+
|
|
122
|
+
- job_name: 'htm-demo'
|
|
123
|
+
scrape_interval: 5s
|
|
124
|
+
static_configs:
|
|
125
|
+
- targets: ['localhost:9394']
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Grafana can't connect to Prometheus
|
|
129
|
+
|
|
130
|
+
1. Verify Prometheus is running:
|
|
131
|
+
```bash
|
|
132
|
+
curl http://localhost:9090/api/v1/status/config
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
2. If connection refused, restart Prometheus:
|
|
136
|
+
```bash
|
|
137
|
+
brew services restart prometheus
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### No metrics in Grafana
|
|
141
|
+
|
|
142
|
+
1. Verify the demo is running and exposing metrics:
|
|
143
|
+
```bash
|
|
144
|
+
curl http://localhost:9394/metrics
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
2. Check Prometheus is scraping the target:
|
|
148
|
+
- Open http://localhost:9090/targets
|
|
149
|
+
- Look for `htm-demo` target with state "UP"
|
|
150
|
+
|
|
151
|
+
### Port conflicts
|
|
152
|
+
|
|
153
|
+
If port 9394 is in use, edit `demo.rb` and change `METRICS_PORT`.
|
|
154
|
+
|
|
155
|
+
## Stopping Services
|
|
156
|
+
|
|
157
|
+
The demo leaves services running for convenience. To stop them:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
brew services stop prometheus grafana
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Service Endpoints
|
|
164
|
+
|
|
165
|
+
| Service | URL | Purpose |
|
|
166
|
+
|---------|-----|---------|
|
|
167
|
+
| Demo Metrics | http://localhost:9394/metrics | Raw Prometheus metrics |
|
|
168
|
+
| Prometheus | http://localhost:9090 | Metrics storage & queries |
|
|
169
|
+
| Grafana | http://localhost:3000 | Visualization dashboard |
|
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# HTM Telemetry Demo with Grafana Visualization
|
|
5
|
+
#
|
|
6
|
+
# This demo shows HTM metrics in a live Grafana dashboard using
|
|
7
|
+
# locally installed Prometheus and Grafana (via Homebrew).
|
|
8
|
+
#
|
|
9
|
+
# Prerequisites:
|
|
10
|
+
# brew install grafana prometheus
|
|
11
|
+
# gem install prometheus-client webrick
|
|
12
|
+
#
|
|
13
|
+
# Usage:
|
|
14
|
+
# cd examples/telemetry
|
|
15
|
+
# ruby demo.rb
|
|
16
|
+
#
|
|
17
|
+
# The demo will:
|
|
18
|
+
# 1. Check/start Prometheus and Grafana
|
|
19
|
+
# 2. Clean up any previous demo data
|
|
20
|
+
# 3. Run HTM operations and export metrics
|
|
21
|
+
# 4. Open Grafana in your browser
|
|
22
|
+
|
|
23
|
+
require 'fileutils'
|
|
24
|
+
|
|
25
|
+
ROBOT_NAME = "Telemetry Demo Robot"
|
|
26
|
+
METRICS_PORT = 9394
|
|
27
|
+
|
|
28
|
+
class TelemetryDemo
|
|
29
|
+
def initialize
|
|
30
|
+
@metrics_server = nil
|
|
31
|
+
@metrics_server_thread = nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def run
|
|
35
|
+
puts banner
|
|
36
|
+
check_ruby_dependencies
|
|
37
|
+
load_htm
|
|
38
|
+
check_brew_services
|
|
39
|
+
start_services
|
|
40
|
+
configure_prometheus_scrape
|
|
41
|
+
setup_metrics_server
|
|
42
|
+
cleanup_previous_data
|
|
43
|
+
import_grafana_dashboard
|
|
44
|
+
open_grafana
|
|
45
|
+
run_demo_loop
|
|
46
|
+
ensure
|
|
47
|
+
stop_metrics_server
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def banner
|
|
53
|
+
<<~BANNER
|
|
54
|
+
|
|
55
|
+
╔══════════════════════════════════════════════════════════════════╗
|
|
56
|
+
║ HTM Telemetry Demo - Live Grafana Visualization ║
|
|
57
|
+
╚══════════════════════════════════════════════════════════════════╝
|
|
58
|
+
|
|
59
|
+
BANNER
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# =========================================================================
|
|
63
|
+
# Dependency Checks
|
|
64
|
+
# =========================================================================
|
|
65
|
+
|
|
66
|
+
def check_ruby_dependencies
|
|
67
|
+
puts "Checking Ruby dependencies..."
|
|
68
|
+
|
|
69
|
+
missing = []
|
|
70
|
+
|
|
71
|
+
# Check for prometheus-client (installed as system gem)
|
|
72
|
+
begin
|
|
73
|
+
gem 'prometheus-client'
|
|
74
|
+
require 'prometheus/client'
|
|
75
|
+
require 'prometheus/client/formats/text'
|
|
76
|
+
puts " [OK] prometheus-client gem"
|
|
77
|
+
rescue LoadError, Gem::MissingSpecError
|
|
78
|
+
missing << 'prometheus-client'
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Check for webrick (installed as system gem)
|
|
82
|
+
begin
|
|
83
|
+
gem 'webrick'
|
|
84
|
+
require 'webrick'
|
|
85
|
+
puts " [OK] webrick gem"
|
|
86
|
+
rescue LoadError, Gem::MissingSpecError
|
|
87
|
+
missing << 'webrick'
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
unless missing.empty?
|
|
91
|
+
puts
|
|
92
|
+
puts " Missing gems. Install with:"
|
|
93
|
+
puts " gem install #{missing.join(' ')}"
|
|
94
|
+
puts
|
|
95
|
+
exit 1
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
puts
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def load_htm
|
|
102
|
+
puts "Loading HTM..."
|
|
103
|
+
|
|
104
|
+
# Use bundler for HTM and its dependencies
|
|
105
|
+
require 'bundler/setup'
|
|
106
|
+
require_relative '../../lib/htm'
|
|
107
|
+
|
|
108
|
+
puts " [OK] HTM #{HTM::VERSION}"
|
|
109
|
+
puts
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def check_brew_services
|
|
113
|
+
puts "Checking Homebrew services..."
|
|
114
|
+
|
|
115
|
+
%w[prometheus grafana].each do |service|
|
|
116
|
+
print " #{service}: "
|
|
117
|
+
|
|
118
|
+
# Check if installed
|
|
119
|
+
result = `brew list #{service} 2>/dev/null`
|
|
120
|
+
if $?.success?
|
|
121
|
+
puts "installed"
|
|
122
|
+
else
|
|
123
|
+
puts "NOT INSTALLED"
|
|
124
|
+
puts
|
|
125
|
+
puts " Install with:"
|
|
126
|
+
puts " brew install #{service}"
|
|
127
|
+
puts
|
|
128
|
+
exit 1
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
puts
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def start_services
|
|
136
|
+
puts "Starting services..."
|
|
137
|
+
|
|
138
|
+
%w[prometheus grafana].each do |service|
|
|
139
|
+
print " #{service}: "
|
|
140
|
+
|
|
141
|
+
if service_running?(service)
|
|
142
|
+
puts "already running"
|
|
143
|
+
else
|
|
144
|
+
# Start the service
|
|
145
|
+
`brew services start #{service} 2>/dev/null`
|
|
146
|
+
sleep 2
|
|
147
|
+
|
|
148
|
+
# Verify it started
|
|
149
|
+
if service_running?(service)
|
|
150
|
+
puts "started"
|
|
151
|
+
else
|
|
152
|
+
puts "FAILED TO START"
|
|
153
|
+
puts " Try: brew services start #{service}"
|
|
154
|
+
exit 1
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Give services a moment to fully initialize
|
|
160
|
+
sleep 2
|
|
161
|
+
puts
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def service_running?(service)
|
|
165
|
+
status = `brew services info #{service} --json 2>/dev/null`
|
|
166
|
+
# Handle both "running": true and "running":true formats
|
|
167
|
+
status.include?('"running": true') || status.include?('"running":true')
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# =========================================================================
|
|
171
|
+
# Prometheus Configuration
|
|
172
|
+
# =========================================================================
|
|
173
|
+
|
|
174
|
+
def configure_prometheus_scrape
|
|
175
|
+
puts "Configuring Prometheus to scrape demo metrics..."
|
|
176
|
+
|
|
177
|
+
require 'yaml'
|
|
178
|
+
|
|
179
|
+
# The actual prometheus.yml location
|
|
180
|
+
prometheus_yml = "/opt/homebrew/etc/prometheus.yml"
|
|
181
|
+
prometheus_yml = "/usr/local/etc/prometheus.yml" unless File.exist?(prometheus_yml)
|
|
182
|
+
|
|
183
|
+
unless File.exist?(prometheus_yml)
|
|
184
|
+
puts " [WARN] Could not find prometheus.yml"
|
|
185
|
+
puts " Metrics may not be scraped automatically."
|
|
186
|
+
puts " Add this to your prometheus.yml:"
|
|
187
|
+
puts
|
|
188
|
+
puts " scrape_configs:"
|
|
189
|
+
puts " - job_name: 'htm-demo'"
|
|
190
|
+
puts " static_configs:"
|
|
191
|
+
puts " - targets: ['localhost:#{METRICS_PORT}']"
|
|
192
|
+
puts
|
|
193
|
+
return
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Parse YAML properly
|
|
197
|
+
config = YAML.load_file(prometheus_yml)
|
|
198
|
+
config['scrape_configs'] ||= []
|
|
199
|
+
|
|
200
|
+
# Check if our job already exists
|
|
201
|
+
job_exists = config['scrape_configs'].any? do |job|
|
|
202
|
+
job['job_name'] == 'htm-demo'
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
if job_exists
|
|
206
|
+
puts " [OK] htm-demo job already configured"
|
|
207
|
+
else
|
|
208
|
+
# Add our scrape config
|
|
209
|
+
htm_job = {
|
|
210
|
+
'job_name' => 'htm-demo',
|
|
211
|
+
'scrape_interval' => '5s',
|
|
212
|
+
'static_configs' => [
|
|
213
|
+
{ 'targets' => ["localhost:#{METRICS_PORT}"] }
|
|
214
|
+
]
|
|
215
|
+
}
|
|
216
|
+
config['scrape_configs'] << htm_job
|
|
217
|
+
|
|
218
|
+
# Write back with proper YAML formatting
|
|
219
|
+
File.write(prometheus_yml, YAML.dump(config))
|
|
220
|
+
puts " [OK] Added htm-demo scrape job"
|
|
221
|
+
|
|
222
|
+
# Restart Prometheus to pick up new config
|
|
223
|
+
print " Restarting Prometheus... "
|
|
224
|
+
`brew services restart prometheus 2>/dev/null`
|
|
225
|
+
sleep 3
|
|
226
|
+
puts "done"
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
puts
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# =========================================================================
|
|
233
|
+
# Metrics Server (exposes /metrics for Prometheus to scrape)
|
|
234
|
+
# =========================================================================
|
|
235
|
+
|
|
236
|
+
def setup_metrics_server
|
|
237
|
+
puts "Starting metrics server on port #{METRICS_PORT}..."
|
|
238
|
+
|
|
239
|
+
# Create Prometheus registry and metrics
|
|
240
|
+
@registry = Prometheus::Client.registry
|
|
241
|
+
|
|
242
|
+
@jobs_counter = @registry.counter(
|
|
243
|
+
:htm_jobs_total,
|
|
244
|
+
docstring: 'HTM job execution counts',
|
|
245
|
+
labels: [:job, :status]
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
@embedding_histogram = @registry.histogram(
|
|
249
|
+
:htm_embedding_latency_milliseconds,
|
|
250
|
+
docstring: 'Embedding generation latency',
|
|
251
|
+
labels: [:provider, :status],
|
|
252
|
+
buckets: [10, 25, 50, 100, 250, 500, 1000, 2500, 5000]
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
@tag_histogram = @registry.histogram(
|
|
256
|
+
:htm_tag_latency_milliseconds,
|
|
257
|
+
docstring: 'Tag extraction latency',
|
|
258
|
+
labels: [:provider, :status],
|
|
259
|
+
buckets: [100, 250, 500, 1000, 2500, 5000, 10000]
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
@search_histogram = @registry.histogram(
|
|
263
|
+
:htm_search_latency_milliseconds,
|
|
264
|
+
docstring: 'Search operation latency',
|
|
265
|
+
labels: [:strategy],
|
|
266
|
+
buckets: [5, 10, 25, 50, 100, 250, 500, 1000]
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
@cache_counter = @registry.counter(
|
|
270
|
+
:htm_cache_operations_total,
|
|
271
|
+
docstring: 'Cache hit/miss counts',
|
|
272
|
+
labels: [:operation]
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# Start WEBrick server in background thread
|
|
276
|
+
@metrics_server = WEBrick::HTTPServer.new(
|
|
277
|
+
Port: METRICS_PORT,
|
|
278
|
+
Logger: WEBrick::Log.new("/dev/null"),
|
|
279
|
+
AccessLog: []
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
@metrics_server.mount_proc '/metrics' do |req, res|
|
|
283
|
+
res['Content-Type'] = 'text/plain; version=0.0.4'
|
|
284
|
+
res.body = Prometheus::Client::Formats::Text.marshal(@registry)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
@metrics_server_thread = Thread.new { @metrics_server.start }
|
|
288
|
+
|
|
289
|
+
puts " [OK] Metrics available at http://localhost:#{METRICS_PORT}/metrics"
|
|
290
|
+
puts
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def stop_metrics_server
|
|
294
|
+
if @metrics_server
|
|
295
|
+
@metrics_server.shutdown
|
|
296
|
+
@metrics_server_thread&.join(2)
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# =========================================================================
|
|
301
|
+
# Data Cleanup
|
|
302
|
+
# =========================================================================
|
|
303
|
+
|
|
304
|
+
def cleanup_previous_data
|
|
305
|
+
puts "Cleaning up previous demo data..."
|
|
306
|
+
|
|
307
|
+
begin
|
|
308
|
+
# Find the robot
|
|
309
|
+
robot = HTM::Models::Robot.find_by(name: ROBOT_NAME)
|
|
310
|
+
|
|
311
|
+
if robot
|
|
312
|
+
# Find all nodes associated with this robot
|
|
313
|
+
node_ids = HTM::Models::RobotNode.where(robot_id: robot.id).pluck(:node_id)
|
|
314
|
+
|
|
315
|
+
if node_ids.any?
|
|
316
|
+
# Hard delete the nodes
|
|
317
|
+
deleted_count = HTM::Models::Node.where(id: node_ids).delete_all
|
|
318
|
+
|
|
319
|
+
# Clean up robot_nodes join table
|
|
320
|
+
HTM::Models::RobotNode.where(robot_id: robot.id).delete_all
|
|
321
|
+
|
|
322
|
+
# Clean up any orphaned node_tags
|
|
323
|
+
HTM::Models::NodeTag.where(node_id: node_ids).delete_all
|
|
324
|
+
|
|
325
|
+
puts " [OK] Deleted #{deleted_count} previous demo nodes"
|
|
326
|
+
else
|
|
327
|
+
puts " [OK] No previous demo data found"
|
|
328
|
+
end
|
|
329
|
+
else
|
|
330
|
+
puts " [OK] No previous demo robot found"
|
|
331
|
+
end
|
|
332
|
+
rescue => e
|
|
333
|
+
puts " [WARN] Cleanup failed: #{e.message}"
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
puts
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
# =========================================================================
|
|
340
|
+
# Grafana Dashboard
|
|
341
|
+
# =========================================================================
|
|
342
|
+
|
|
343
|
+
def import_grafana_dashboard
|
|
344
|
+
puts "Grafana dashboard setup..."
|
|
345
|
+
puts " Dashboard JSON: examples/telemetry/grafana/dashboards/htm-metrics.json"
|
|
346
|
+
puts " To import: Grafana > Dashboards > Import > Upload JSON"
|
|
347
|
+
puts
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def open_grafana
|
|
351
|
+
puts "Opening Grafana..."
|
|
352
|
+
system("open http://localhost:3000/d/htm-metrics/htm-metrics 2>/dev/null || " \
|
|
353
|
+
"xdg-open http://localhost:3000 2>/dev/null || " \
|
|
354
|
+
"echo ' Open http://localhost:3000 in your browser'")
|
|
355
|
+
puts " Default login: admin / admin"
|
|
356
|
+
puts
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# =========================================================================
|
|
360
|
+
# Demo Loop
|
|
361
|
+
# =========================================================================
|
|
362
|
+
|
|
363
|
+
def run_demo_loop
|
|
364
|
+
puts "=" * 60
|
|
365
|
+
puts "Starting demo loop..."
|
|
366
|
+
puts " Metrics: http://localhost:#{METRICS_PORT}/metrics"
|
|
367
|
+
puts " Grafana: http://localhost:3000"
|
|
368
|
+
puts "=" * 60
|
|
369
|
+
puts
|
|
370
|
+
puts "Press Ctrl+C to stop"
|
|
371
|
+
puts
|
|
372
|
+
|
|
373
|
+
# Quiet loggers
|
|
374
|
+
HTM.configure do |config|
|
|
375
|
+
config.logger = Logger.new(File::NULL)
|
|
376
|
+
end
|
|
377
|
+
RubyLLM.logger = Logger.new(File::NULL) if defined?(RubyLLM)
|
|
378
|
+
|
|
379
|
+
htm = HTM.new(robot_name: ROBOT_NAME)
|
|
380
|
+
iteration = 0
|
|
381
|
+
|
|
382
|
+
loop do
|
|
383
|
+
iteration += 1
|
|
384
|
+
puts "[#{Time.now.strftime('%H:%M:%S')}] Iteration #{iteration}"
|
|
385
|
+
|
|
386
|
+
# Remember something (track embedding + tag metrics)
|
|
387
|
+
content = sample_content(iteration)
|
|
388
|
+
print " > Remember: #{content[0..45]}... "
|
|
389
|
+
|
|
390
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
391
|
+
node_id = htm.remember(content)
|
|
392
|
+
puts "node #{node_id}"
|
|
393
|
+
|
|
394
|
+
# Wait for background jobs and record metrics
|
|
395
|
+
sleep 3
|
|
396
|
+
|
|
397
|
+
# Simulate job completion metrics (in real app, jobs would record these)
|
|
398
|
+
record_job_metrics
|
|
399
|
+
|
|
400
|
+
# Search with different strategies
|
|
401
|
+
%w[fulltext vector hybrid].each do |strategy|
|
|
402
|
+
query = sample_query(iteration)
|
|
403
|
+
print " > Recall (#{strategy.ljust(8)}): '#{query.ljust(12)}' "
|
|
404
|
+
|
|
405
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
406
|
+
results = htm.recall(query, strategy: strategy.to_sym, limit: 3)
|
|
407
|
+
elapsed_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round
|
|
408
|
+
|
|
409
|
+
@search_histogram.observe(elapsed_ms, labels: { strategy: strategy })
|
|
410
|
+
|
|
411
|
+
# Simulate cache behavior
|
|
412
|
+
if rand < 0.3
|
|
413
|
+
@cache_counter.increment(labels: { operation: 'hit' })
|
|
414
|
+
else
|
|
415
|
+
@cache_counter.increment(labels: { operation: 'miss' })
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
puts "-> #{results.length} results (#{elapsed_ms}ms)"
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
puts " Metrics exported to Prometheus"
|
|
422
|
+
puts
|
|
423
|
+
|
|
424
|
+
sleep 5
|
|
425
|
+
end
|
|
426
|
+
rescue Interrupt
|
|
427
|
+
puts
|
|
428
|
+
puts "Shutting down..."
|
|
429
|
+
ask_to_stop_services
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
def ask_to_stop_services
|
|
433
|
+
print "\nStop Prometheus and Grafana services? (y/N): "
|
|
434
|
+
response = $stdin.gets&.strip&.downcase
|
|
435
|
+
|
|
436
|
+
if response == 'y' || response == 'yes'
|
|
437
|
+
print " Stopping prometheus... "
|
|
438
|
+
`brew services stop prometheus 2>/dev/null`
|
|
439
|
+
puts "done"
|
|
440
|
+
|
|
441
|
+
print " Stopping grafana... "
|
|
442
|
+
`brew services stop grafana 2>/dev/null`
|
|
443
|
+
puts "done"
|
|
444
|
+
|
|
445
|
+
puts " Services stopped."
|
|
446
|
+
else
|
|
447
|
+
puts " Services left running."
|
|
448
|
+
puts " To stop later: brew services stop prometheus grafana"
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
def record_job_metrics
|
|
453
|
+
# Record successful embedding job
|
|
454
|
+
@jobs_counter.increment(labels: { job: 'embedding', status: 'success' })
|
|
455
|
+
@embedding_histogram.observe(
|
|
456
|
+
rand(50..200),
|
|
457
|
+
labels: { provider: 'ollama', status: 'success' }
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
# Record successful tag job
|
|
461
|
+
@jobs_counter.increment(labels: { job: 'tags', status: 'success' })
|
|
462
|
+
@tag_histogram.observe(
|
|
463
|
+
rand(500..2000),
|
|
464
|
+
labels: { provider: 'ollama', status: 'success' }
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
# Occasionally record failures for demo variety
|
|
468
|
+
if rand < 0.1
|
|
469
|
+
@jobs_counter.increment(labels: { job: 'embedding', status: 'error' })
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
def sample_content(iteration)
|
|
474
|
+
contents = [
|
|
475
|
+
"PostgreSQL supports vector similarity search through the pgvector extension.",
|
|
476
|
+
"OpenTelemetry provides unified observability for distributed systems.",
|
|
477
|
+
"Ruby on Rails is a popular web application framework.",
|
|
478
|
+
"Machine learning models can be used for semantic search.",
|
|
479
|
+
"The HTM gem provides intelligent memory management for LLM applications.",
|
|
480
|
+
"Grafana is an open-source analytics and monitoring platform.",
|
|
481
|
+
"Prometheus is a systems monitoring and alerting toolkit.",
|
|
482
|
+
"Background job processing improves application responsiveness.",
|
|
483
|
+
"Hierarchical tags help organize information semantically.",
|
|
484
|
+
"Vector embeddings capture semantic meaning of text."
|
|
485
|
+
]
|
|
486
|
+
contents[(iteration - 1) % contents.length]
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
def sample_query(iteration)
|
|
490
|
+
queries = %w[database observability ruby search memory monitoring metrics jobs tags vectors]
|
|
491
|
+
queries[(iteration - 1) % queries.length]
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
# Run the demo
|
|
496
|
+
if __FILE__ == $PROGRAM_NAME
|
|
497
|
+
TelemetryDemo.new.run
|
|
498
|
+
end
|