This repository has been archived by the owner on Oct 30, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathanalysis.coffee
143 lines (120 loc) · 4.3 KB
/
analysis.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
{ Governor } = require './governor'
config = require './config'
# note: names lowercased
costsHourly =
'standard-1x': 0.035 # 25 usd/mnt
'standard-2x': 0.070 # 50
'performance-m': 0.350 # 250
'performance-l': 0.700 # 500
'1x': 0.050 # legacy
'2x': 0.100 # legacy
'px': 0.800 # legacy
calculateCosts = (computeSeconds, dynoType) ->
costs = {}
for role, seconds of computeSeconds
type = dynoType[role]
type = 'standard-1x' if not type
type = type.toLowerCase()
hours = (seconds/(60*60))
cost = costsHourly[type]*hours
costs[role] = cost
return costs
# Accumulate the compute time used
class ComputeTimer
constructor: () ->
@accumulated = {} # role -> seconds
@previousTimes = {} # role -> timestamp
addState: (state, times) ->
for role, s of state
newTime = times[role]
#console.log 'ss', role, s.current_workers
@accumulated[role] = 0 if not @accumulated[role]?
if @previousTimes[role]
timeDiff = Math.ceil((newTime - @previousTimes[role])/(1000))
increment = (timeDiff * s.current_workers)
@accumulated[role] += increment
if s.new_workers and s.previous_workers
# compensate for boot-up/shutdown time?
bootTime = 60
#console.log 'adding', s.new_workers - s.previous_workers
change = Math.abs(s.new_workers - s.previous_workers)
@accumulated[role] += bootTime * change
@previousTimes[role] = newTime
arrayEquals = (a, b) ->
A = a.toString()
B = b.toString()
return A == B # Lazy
queueDataFromEvents = (cfg, events) ->
# sort events time-wise
events = events.sort (a, b) -> (a.timestamp - b.timestamp)
# calculate full set of queue data, as they would be returned by RabbitMQ
allRoles = Object.keys(cfg).filter((r) -> r != '*').sort()
data = []
lastTimestamp = 0
lastByRole = {}
for e in events
if e.timestamp < lastTimestamp
console.log 'WARN: unordered event data', e.timestamp, lastTimestamp
lastByRole[e.role] = e
lastTimestamp = e.timestamp
haveRoles = Object.keys(lastByRole).sort()
#console.log haveRoles, allRoles
if arrayEquals haveRoles, allRoles
queues = {}
timestamps = {}
for role, v of lastByRole
queue = cfg[role].queue
queues[queue] = v.jobs
timestamps[role] = v.timestamp
#console.log 'full', queues, lastByRole
data.push
queues: queues
timestamps: timestamps
lastByRole = {}
return data
# with a given config
# replay a set of GuvScaled events
# invariant: events are sorted according to increasing time
# determine an initial stable state, where we have internal state for all roles
# from this time on, calculate number of worker-compute-seconds (or minutes) we have
# based on this data, allow calculating cost (per role, per app, total)
#
# TODO: allow filtering on app?
# TODO: store some config identifier into events? hash of normalized config? so we can detect changes
# TODO: allow calculating min and max, costs from a config
# TODO: allow calculating typical costs, given total number of events for period
main = () ->
# node.js only
fs = require 'fs'
[ interpreter, prog, configFile, eventFile ] = process.argv
c = fs.readFileSync configFile
cfg = config.parse c
governor = new Governor cfg
compute = new ComputeTimer
#console.log governor.config
events = JSON.parse(fs.readFileSync(eventFile))
console.log "got #{events.length} events\n"
# XXX: have to combine all events at a given timestamp, to give queue data for everything at once
# if history was done per role instead of globally, this would not be neccesary
queueData = queueDataFromEvents cfg, events
for d in queueData
try
s = governor.nextState null, d.queues
compute.addState s, d.timestamps # TODO: keep timestamp state internally?
catch e
console.log d.queues, s?, e
console.log governor.history
console.log e.stack
types = {}
for role, val of cfg
types[role] = val.dynosize
# console.log 'dyno sizes', types
# console.log 'compute seconds', compute.accumulated
costs = calculateCosts compute.accumulated, types
# console.log 'costs', costs
total = 0
for role, v of costs
console.log "#{role}: #{v.toFixed()} USD"
total += v
console.log "Total: #{total.toFixed()} USD"
main() if not module.parent