lex-metering 0.1.12 → 0.1.13
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/.github/workflows/ci.yml +4 -4
- data/.rubocop.yml +2 -59
- data/CHANGELOG.md +5 -0
- data/CLAUDE.md +44 -13
- data/Gemfile +1 -0
- data/lib/legion/extensions/metering/actors/cleanup.rb +1 -1
- data/lib/legion/extensions/metering/actors/cost_optimizer.rb +1 -1
- data/lib/legion/extensions/metering/actors/rollup.rb +1 -1
- data/lib/legion/extensions/metering/helpers/economics.rb +1 -1
- data/lib/legion/extensions/metering/runners/cost_optimizer.rb +6 -4
- data/lib/legion/extensions/metering/runners/metering.rb +7 -5
- data/lib/legion/extensions/metering/runners/rollup.rb +7 -5
- data/lib/legion/extensions/metering/version.rb +1 -1
- data/lib/legion/extensions/metering.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d74b4188d55fe8f0b4b904f04345a241b09040eec1104d5c4dcfb83ebfd6a17e
|
|
4
|
+
data.tar.gz: 38bb6a9e985e52d2dcdf12bb4e1b7223e0f0c0b13c7705ea5c365618e356537c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6a3458e571639fdf876cf85247885e62591b6e0fb2b36a3e49f4039dd0cfe293eb5b78c244c0fa6e200c8c1c5a97b458acd4b759409eca603dd4f31d468f5f57
|
|
7
|
+
data.tar.gz: 283af82b8353acd86eb1b2446a97b03a4f415e352cbaf06b2807ae145b2f92da1a1c05419785f42143dba60c31aa77325972eef0cd9d8eccea4c56f6d3b45ad4
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -10,8 +10,8 @@ jobs:
|
|
|
10
10
|
ci:
|
|
11
11
|
uses: LegionIO/.github/.github/workflows/ci.yml@main
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
uses: LegionIO/.github/.github/workflows/
|
|
13
|
+
excluded-files:
|
|
14
|
+
uses: LegionIO/.github/.github/workflows/excluded-files.yml@main
|
|
15
15
|
|
|
16
16
|
security:
|
|
17
17
|
uses: LegionIO/.github/.github/workflows/security-scan.yml@main
|
|
@@ -27,8 +27,8 @@ jobs:
|
|
|
27
27
|
uses: LegionIO/.github/.github/workflows/stale.yml@main
|
|
28
28
|
|
|
29
29
|
release:
|
|
30
|
-
needs: [ci,
|
|
30
|
+
needs: [ci, excluded-files]
|
|
31
31
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
32
32
|
uses: LegionIO/.github/.github/workflows/release.yml@main
|
|
33
33
|
secrets:
|
|
34
|
-
rubygems-api-key: ${{ secrets.RUBYGEMS_API_KEY }}
|
|
34
|
+
rubygems-api-key: ${{ secrets.RUBYGEMS_API_KEY }}
|
data/.rubocop.yml
CHANGED
|
@@ -1,59 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
NewCops: enable
|
|
4
|
-
SuggestExtensions: false
|
|
5
|
-
|
|
6
|
-
Layout/LineLength:
|
|
7
|
-
Max: 160
|
|
8
|
-
|
|
9
|
-
Layout/SpaceAroundEqualsInParameterDefault:
|
|
10
|
-
EnforcedStyle: space
|
|
11
|
-
|
|
12
|
-
Layout/HashAlignment:
|
|
13
|
-
EnforcedHashRocketStyle: table
|
|
14
|
-
EnforcedColonStyle: table
|
|
15
|
-
|
|
16
|
-
Metrics/MethodLength:
|
|
17
|
-
Max: 50
|
|
18
|
-
|
|
19
|
-
Metrics/ClassLength:
|
|
20
|
-
Max: 1500
|
|
21
|
-
|
|
22
|
-
Metrics/ModuleLength:
|
|
23
|
-
Max: 1500
|
|
24
|
-
|
|
25
|
-
Metrics/BlockLength:
|
|
26
|
-
Max: 40
|
|
27
|
-
Exclude:
|
|
28
|
-
- 'spec/**/*'
|
|
29
|
-
|
|
30
|
-
Metrics/AbcSize:
|
|
31
|
-
Max: 60
|
|
32
|
-
|
|
33
|
-
Metrics/CyclomaticComplexity:
|
|
34
|
-
Max: 15
|
|
35
|
-
|
|
36
|
-
Metrics/PerceivedComplexity:
|
|
37
|
-
Max: 17
|
|
38
|
-
|
|
39
|
-
Style/Documentation:
|
|
40
|
-
Enabled: false
|
|
41
|
-
|
|
42
|
-
Style/SymbolArray:
|
|
43
|
-
Enabled: true
|
|
44
|
-
|
|
45
|
-
Style/FrozenStringLiteralComment:
|
|
46
|
-
Enabled: true
|
|
47
|
-
EnforcedStyle: always
|
|
48
|
-
|
|
49
|
-
Naming/FileName:
|
|
50
|
-
Enabled: false
|
|
51
|
-
|
|
52
|
-
Naming/PredicateMethod:
|
|
53
|
-
Enabled: false
|
|
54
|
-
|
|
55
|
-
Gemspec/DevelopmentDependencies:
|
|
56
|
-
Enabled: false
|
|
57
|
-
|
|
58
|
-
Metrics/ParameterLists:
|
|
59
|
-
Enabled: false
|
|
1
|
+
inherit_gem:
|
|
2
|
+
rubocop-legion: config/lex.yml
|
data/CHANGELOG.md
CHANGED
data/CLAUDE.md
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
## Purpose
|
|
8
8
|
|
|
9
|
-
Captures LLM token usage metrics per task for cost attribution and intelligent routing. Records input/output/thinking tokens, latency, wall-clock time, CPU time, external API call counts, and routing reason per metered call. Supports per-worker, per-team, and aggregate routing statistics queries.
|
|
9
|
+
Captures LLM token usage metrics per task for cost attribution and intelligent routing. Records input/output/thinking tokens, latency, wall-clock time, CPU time, external API call counts, cost in USD, trace context (extension, runner_function, event_type, status), and routing reason per metered call. Supports per-worker, per-team, and aggregate routing statistics queries. Hourly rollup to a summary table for reporting efficiency.
|
|
10
10
|
|
|
11
11
|
## Gem Info
|
|
12
12
|
|
|
13
13
|
- **Gem name**: `lex-metering`
|
|
14
|
-
- **Version**: `0.1.
|
|
14
|
+
- **Version**: `0.1.11`
|
|
15
15
|
- **Module**: `Legion::Extensions::Metering`
|
|
16
16
|
- **Ruby**: `>= 3.4`
|
|
17
17
|
- **License**: MIT
|
|
@@ -23,12 +23,17 @@ Captures LLM token usage metrics per task for cost attribution and intelligent r
|
|
|
23
23
|
lib/legion/extensions/metering/
|
|
24
24
|
version.rb
|
|
25
25
|
actors/
|
|
26
|
-
cleanup.rb
|
|
26
|
+
cleanup.rb # Every actor (86400s/daily): calls cleanup_old_records
|
|
27
|
+
cost_optimizer.rb # Every actor (604800s/weekly): calls analyze_costs; Singleton mixin
|
|
28
|
+
rollup.rb # Every actor (3600s/hourly): calls rollup_hour; run_now? false
|
|
27
29
|
data/
|
|
28
30
|
migrations/
|
|
29
|
-
001_add_metering_records.rb # Creates metering_records table
|
|
31
|
+
001_add_metering_records.rb # Creates metering_records table (base schema)
|
|
32
|
+
002_add_trace_columns.rb # Adds cost_usd, status, event_type, extension, runner_function
|
|
30
33
|
runners/
|
|
31
|
-
metering.rb
|
|
34
|
+
metering.rb # record, worker_costs, team_costs, routing_stats, cleanup_old_records
|
|
35
|
+
cost_optimizer.rb # analyze_costs (LLM-powered model rightsizing recommendations)
|
|
36
|
+
rollup.rb # rollup_hour, purge_raw_records
|
|
32
37
|
```
|
|
33
38
|
|
|
34
39
|
## Database Schema (`metering_records`)
|
|
@@ -49,34 +54,60 @@ lib/legion/extensions/metering/
|
|
|
49
54
|
| `cpu_time_ms` | Integer | CPU time consumed |
|
|
50
55
|
| `external_api_calls` | Integer | Non-LLM external API calls made |
|
|
51
56
|
| `routing_reason` | String(255) | Why this model/provider was chosen |
|
|
57
|
+
| `cost_usd` | Float | Estimated cost in USD (default: 0.0) |
|
|
58
|
+
| `status` | String(50) | Pipeline status at time of record |
|
|
59
|
+
| `event_type` | String(100) | Event type for SIEM/audit correlation |
|
|
60
|
+
| `extension` | String(255) | Calling extension name |
|
|
61
|
+
| `runner_function` | String(255) | Calling runner function name |
|
|
52
62
|
| `recorded_at` | DateTime | Timestamp (indexed) |
|
|
53
63
|
|
|
54
64
|
## Runner Methods
|
|
55
65
|
|
|
56
66
|
| Method | Parameters | Returns |
|
|
57
67
|
|--------|-----------|---------|
|
|
58
|
-
| `record` | All schema fields as kwargs | Record hash (also inserted to DB) |
|
|
68
|
+
| `record` | All schema fields as kwargs | Record hash (also inserted to DB when connected) |
|
|
59
69
|
| `worker_costs` | `worker_id:`, `period: 'daily'` | Aggregated token/call/latency metrics |
|
|
60
70
|
| `team_costs` | `team:`, `period: 'daily'` | Team-wide aggregation across all team workers |
|
|
61
71
|
| `routing_stats` | `worker_id: nil` | Breakdowns by routing_reason, provider, model, avg latency |
|
|
62
|
-
| `cleanup_old_records` | `retention_days: 90` | Deletes records older than cutoff
|
|
72
|
+
| `cleanup_old_records` | `retention_days: 90` | Deletes records older than cutoff |
|
|
63
73
|
|
|
64
74
|
`period` values: `'daily'`, `'weekly'`, `'monthly'`
|
|
65
75
|
|
|
66
|
-
|
|
76
|
+
### Rollup (`Runners::Rollup`)
|
|
67
77
|
|
|
68
|
-
`
|
|
78
|
+
`rollup_hour` — groups `metering_records` by worker/provider/model for the current hour into `metering_hourly_rollup` table with upsert semantics (one row per worker+provider+model+hour).
|
|
79
|
+
|
|
80
|
+
`purge_raw_records(retention_days: 7)` — deletes raw `metering_records` older than the retention window after they have been rolled up.
|
|
81
|
+
|
|
82
|
+
### CostOptimizer (`Runners::CostOptimizer`)
|
|
83
|
+
|
|
84
|
+
`analyze_costs(window_days: 7, top_n: 10)` — aggregates cost drivers from `metering_records` for the past `window_days` days. Calls `Legion::LLM.chat` (with `caller: { extension: 'lex-metering', operation: 'cost_optimization' }`) to generate model rightsizing recommendations. Returns `{ status:, cost_drivers:, recommendations: }`.
|
|
85
|
+
|
|
86
|
+
Built-in rate table (per 1M tokens): Anthropic claude-opus-4-6 $15, claude-sonnet-4-6 $3, claude-haiku-4-5 $0.25; OpenAI gpt-4o $5, gpt-4o-mini $0.15; Bedrock/Azure default $3.
|
|
87
|
+
|
|
88
|
+
## Actors
|
|
89
|
+
|
|
90
|
+
| Actor | Interval | Behaviour |
|
|
91
|
+
|-------|----------|-----------|
|
|
92
|
+
| `Cleanup` | 86400s daily | `cleanup_old_records`; `use_runner? false` |
|
|
93
|
+
| `CostOptimizer` | 604800s weekly | `analyze_costs`; `use_runner? false`; Singleton mixin |
|
|
94
|
+
| `Rollup` | 3600s hourly | `rollup_hour`; `run_now? false` |
|
|
69
95
|
|
|
70
96
|
## Integration Points
|
|
71
97
|
|
|
72
|
-
- **legion-data**: `data_required? false` — loads without DB. `record` returns hash
|
|
98
|
+
- **legion-data**: `data_required? false` — loads without DB. `record` returns hash for RMQ publishing; query methods require `Legion::Data`.
|
|
73
99
|
- **LegionIO MCP**: `legion.routing_stats` MCP tool calls `routing_stats` runner
|
|
74
100
|
- **REST API**: `GET /api/tasks/:id` includes a `:metering` block when lex-metering data exists for the task
|
|
75
101
|
- **Digital Workers**: `legion worker costs` CLI command delegates to `worker_costs` runner
|
|
102
|
+
- **lex-llm-gateway**: MeteringWriter actor writes to the same `metering_records` table
|
|
76
103
|
|
|
77
104
|
## Development Notes
|
|
78
105
|
|
|
79
|
-
-
|
|
80
|
-
- Has one explicit actor (`Cleanup`); auto-generated subscription actors are created for runner methods
|
|
106
|
+
- Actor module is `module Actor` (singular) per framework convention
|
|
81
107
|
- `routing_stats` uses `select_append { avg(latency_ms).as(avg_latency) }` — Sequel virtual row syntax
|
|
82
|
-
- Time interval filtering uses `Sequel.lit('recorded_at >= ?', cutoff)` with Ruby `Time` arithmetic for cross-database compatibility
|
|
108
|
+
- Time interval filtering uses `Sequel.lit('recorded_at >= ?', cutoff)` with Ruby `Time` arithmetic for cross-database compatibility
|
|
109
|
+
- `CostOptimizer` actor includes `Legion::Extensions::Actors::Singleton` when available to prevent duplicate weekly runs in a cluster
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
**Maintained By**: Matthew Iverson (@Esity)
|
data/Gemfile
CHANGED
|
@@ -4,7 +4,7 @@ module Legion
|
|
|
4
4
|
module Extensions
|
|
5
5
|
module Metering
|
|
6
6
|
module Actor
|
|
7
|
-
class Cleanup < Legion::Extensions::Actors::Every
|
|
7
|
+
class Cleanup < Legion::Extensions::Actors::Every # rubocop:disable Legion/Extension/EveryActorRequiresTime
|
|
8
8
|
include Legion::Extensions::Actors::Singleton if defined?(Legion::Extensions::Actors::Singleton)
|
|
9
9
|
|
|
10
10
|
def runner_class
|
|
@@ -4,7 +4,7 @@ module Legion
|
|
|
4
4
|
module Extensions
|
|
5
5
|
module Metering
|
|
6
6
|
module Actor
|
|
7
|
-
class CostOptimizer < Legion::Extensions::Actors::Every
|
|
7
|
+
class CostOptimizer < Legion::Extensions::Actors::Every # rubocop:disable Legion/Extension/EveryActorRequiresTime
|
|
8
8
|
include Legion::Extensions::Actors::Singleton if defined?(Legion::Extensions::Actors::Singleton)
|
|
9
9
|
|
|
10
10
|
def runner_class
|
|
@@ -4,7 +4,7 @@ module Legion
|
|
|
4
4
|
module Extensions
|
|
5
5
|
module Metering
|
|
6
6
|
module Actor
|
|
7
|
-
class Rollup < Legion::Extensions::Actors::Every
|
|
7
|
+
class Rollup < Legion::Extensions::Actors::Every # rubocop:disable Legion/Extension/EveryActorRequiresTime
|
|
8
8
|
include Legion::Extensions::Actors::Singleton if defined?(Legion::Extensions::Actors::Singleton)
|
|
9
9
|
|
|
10
10
|
def runner_class
|
|
@@ -5,6 +5,8 @@ module Legion
|
|
|
5
5
|
module Metering
|
|
6
6
|
module Runners
|
|
7
7
|
module CostOptimizer
|
|
8
|
+
extend self
|
|
9
|
+
|
|
8
10
|
def analyze_costs(window_days: 7, top_n: 10)
|
|
9
11
|
drivers = collect_cost_data(window_days: window_days)
|
|
10
12
|
return { status: 'no_data', window_days: window_days, cost_drivers: [], recommendations: [] } if drivers.empty?
|
|
@@ -23,7 +25,7 @@ module Legion
|
|
|
23
25
|
private
|
|
24
26
|
|
|
25
27
|
def collect_cost_data(window_days:)
|
|
26
|
-
return [] unless defined?(Legion::Data) && Legion::Data.respond_to?(:connection) && Legion::Data.connection
|
|
28
|
+
return [] unless defined?(Legion::Data) && Legion::Data.respond_to?(:connection) && Legion::Data.connection # rubocop:disable Legion/Extension/RunnerReturnHash
|
|
27
29
|
|
|
28
30
|
cutoff = Time.now.utc - (window_days * 86_400)
|
|
29
31
|
ds = Legion::Data.connection[:metering_records]
|
|
@@ -46,7 +48,7 @@ module Legion
|
|
|
46
48
|
call_count: row[:call_count] || 0
|
|
47
49
|
}
|
|
48
50
|
end
|
|
49
|
-
rescue StandardError
|
|
51
|
+
rescue StandardError => _e
|
|
50
52
|
[]
|
|
51
53
|
end
|
|
52
54
|
|
|
@@ -70,9 +72,9 @@ module Legion
|
|
|
70
72
|
return { recommendations: [] } unless defined?(Legion::LLM)
|
|
71
73
|
|
|
72
74
|
prompt = build_recommendation_prompt(drivers)
|
|
73
|
-
result =
|
|
75
|
+
result = llm_chat(message: prompt, caller: { extension: 'lex-metering', operation: 'cost_optimization' })
|
|
74
76
|
::JSON.parse(result[:content] || '{}', symbolize_names: true)
|
|
75
|
-
rescue StandardError
|
|
77
|
+
rescue StandardError => _e
|
|
76
78
|
{ recommendations: [] }
|
|
77
79
|
end
|
|
78
80
|
|
|
@@ -5,9 +5,11 @@ module Legion
|
|
|
5
5
|
module Metering
|
|
6
6
|
module Runners
|
|
7
7
|
module Metering
|
|
8
|
+
extend self
|
|
9
|
+
|
|
8
10
|
PERIOD_DAYS = { 'daily' => 1, 'weekly' => 7, 'monthly' => 30 }.freeze
|
|
9
11
|
|
|
10
|
-
def record(worker_id: nil, task_id: nil, provider: nil, model_id: nil,
|
|
12
|
+
def record(worker_id: nil, task_id: nil, provider: nil, model_id: nil, # rubocop:disable Metrics/ParameterLists
|
|
11
13
|
input_tokens: 0, output_tokens: 0, thinking_tokens: 0,
|
|
12
14
|
input_context_bytes: 0, latency_ms: 0, routing_reason: nil,
|
|
13
15
|
wall_clock_ms: 0, cpu_time_ms: 0, external_api_calls: 0,
|
|
@@ -36,8 +38,8 @@ module Legion
|
|
|
36
38
|
recorded_at: Time.now.utc
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
log.debug("[metering] recorded: provider=#{provider} model=#{model_id} " \
|
|
42
|
+
"tokens=#{record[:total_tokens]} latency=#{latency_ms}ms wall_clock=#{wall_clock_ms}ms")
|
|
41
43
|
record
|
|
42
44
|
end
|
|
43
45
|
|
|
@@ -90,7 +92,7 @@ module Legion
|
|
|
90
92
|
|
|
91
93
|
cutoff = Time.now.utc - (retention_days * 86_400)
|
|
92
94
|
count = Legion::Data.connection[:metering_records].where { recorded_at < cutoff }.delete
|
|
93
|
-
|
|
95
|
+
log.info("[metering] cleanup: purged=#{count} retention_days=#{retention_days} cutoff=#{cutoff}")
|
|
94
96
|
{ purged: count, retention_days: retention_days, cutoff: cutoff }
|
|
95
97
|
end
|
|
96
98
|
|
|
@@ -98,7 +100,7 @@ module Legion
|
|
|
98
100
|
|
|
99
101
|
def apply_period_filter(dataset, period)
|
|
100
102
|
days = PERIOD_DAYS[period]
|
|
101
|
-
return dataset unless days
|
|
103
|
+
return dataset unless days # rubocop:disable Legion/Extension/RunnerReturnHash
|
|
102
104
|
|
|
103
105
|
cutoff = Time.now.utc - (days * 86_400)
|
|
104
106
|
dataset.where(::Sequel.lit('recorded_at >= ?', cutoff))
|
|
@@ -5,6 +5,8 @@ module Legion
|
|
|
5
5
|
module Metering
|
|
6
6
|
module Runners
|
|
7
7
|
module Rollup
|
|
8
|
+
extend self
|
|
9
|
+
|
|
8
10
|
def rollup_hour(hour: nil, **)
|
|
9
11
|
return { status: 'skipped', reason: 'data_unavailable' } unless data_available?
|
|
10
12
|
|
|
@@ -35,7 +37,7 @@ module Legion
|
|
|
35
37
|
rolled_up += 1
|
|
36
38
|
end
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
log.info("[metering] rollup_hour: hour=#{hour.iso8601} groups=#{rolled_up} raw_records=#{raw_count}")
|
|
39
41
|
{ rolled_up: rolled_up, hour: hour.iso8601, raw_records: raw_count }
|
|
40
42
|
end
|
|
41
43
|
|
|
@@ -47,14 +49,14 @@ module Legion
|
|
|
47
49
|
.where(::Sequel.lit('recorded_at < ?', cutoff))
|
|
48
50
|
.delete
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
log.info("[metering] purge_raw_records: purged=#{count} retention_days=#{retention_days} cutoff=#{cutoff.iso8601}")
|
|
51
53
|
{ purged: count, retention_days: retention_days, cutoff: cutoff.iso8601 }
|
|
52
54
|
end
|
|
53
55
|
|
|
54
56
|
private
|
|
55
57
|
|
|
56
58
|
def data_available?(table = nil)
|
|
57
|
-
return false unless defined?(Legion::Data) && Legion::Data.respond_to?(:connection) && Legion::Data.connection
|
|
59
|
+
return false unless defined?(Legion::Data) && Legion::Data.respond_to?(:connection) && Legion::Data.connection # rubocop:disable Legion/Extension/RunnerReturnHash
|
|
58
60
|
|
|
59
61
|
if table
|
|
60
62
|
Legion::Data.connection.table_exists?(table)
|
|
@@ -65,7 +67,7 @@ module Legion
|
|
|
65
67
|
end
|
|
66
68
|
|
|
67
69
|
def resolve_hour(hour)
|
|
68
|
-
return hour if hour
|
|
70
|
+
return hour if hour # rubocop:disable Legion/Extension/RunnerReturnHash
|
|
69
71
|
|
|
70
72
|
now = Time.now.utc
|
|
71
73
|
floored = Time.utc(now.year, now.month, now.day, now.hour)
|
|
@@ -73,7 +75,7 @@ module Legion
|
|
|
73
75
|
end
|
|
74
76
|
|
|
75
77
|
def build_rollup_row(worker_id, provider, model_id, hour, rows)
|
|
76
|
-
latencies = rows.
|
|
78
|
+
latencies = rows.filter_map { |r| r[:latency_ms] }
|
|
77
79
|
avg_latency = latencies.empty? ? 0 : (latencies.sum.to_f / latencies.size).round(2)
|
|
78
80
|
|
|
79
81
|
{
|
|
@@ -7,7 +7,7 @@ require 'legion/extensions/metering/runners/rollup'
|
|
|
7
7
|
module Legion
|
|
8
8
|
module Extensions
|
|
9
9
|
module Metering
|
|
10
|
-
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
|
|
10
|
+
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core, false
|
|
11
11
|
|
|
12
12
|
def self.data_required?
|
|
13
13
|
false
|