github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/worker/metrics/collect/manifold.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // Package collect provides a worker that executes the collect-metrics hook 5 // periodically, as long as the workload has been started (between start and 6 // stop hooks). collect-metrics executes in its own execution context, which is 7 // restricted to avoid contention with uniter "lifecycle" hooks. 8 package collect 9 10 import ( 11 "fmt" 12 "path/filepath" 13 "time" 14 15 "github.com/juju/errors" 16 "github.com/juju/loggo" 17 "github.com/juju/names" 18 corecharm "gopkg.in/juju/charm.v6-unstable" 19 "gopkg.in/juju/charm.v6-unstable/hooks" 20 21 "github.com/juju/juju/agent" 22 "github.com/juju/juju/agent/tools" 23 "github.com/juju/juju/api/base" 24 uniterapi "github.com/juju/juju/api/uniter" 25 "github.com/juju/juju/worker" 26 "github.com/juju/juju/worker/charmdir" 27 "github.com/juju/juju/worker/dependency" 28 "github.com/juju/juju/worker/metrics/spool" 29 "github.com/juju/juju/worker/uniter/runner" 30 "github.com/juju/juju/worker/uniter/runner/context" 31 "github.com/juju/utils/os" 32 ) 33 34 const defaultPeriod = 5 * time.Minute 35 36 var ( 37 logger = loggo.GetLogger("juju.worker.metrics.collect") 38 ) 39 40 // ManifoldConfig identifies the resource names upon which the collect manifold 41 // depends. 42 type ManifoldConfig struct { 43 Period *time.Duration 44 45 AgentName string 46 APICallerName string 47 MetricSpoolName string 48 CharmDirName string 49 } 50 51 // Manifold returns a collect-metrics manifold. 52 func Manifold(config ManifoldConfig) dependency.Manifold { 53 return dependency.Manifold{ 54 Inputs: []string{ 55 config.AgentName, 56 config.APICallerName, 57 config.MetricSpoolName, 58 config.CharmDirName, 59 }, 60 Start: func(getResource dependency.GetResourceFunc) (worker.Worker, error) { 61 collector, err := newCollect(config, getResource) 62 if err != nil { 63 return nil, err 64 } 65 return worker.NewPeriodicWorker(collector.Do, collector.period, worker.NewTimer), nil 66 }, 67 } 68 } 69 70 // UnitCharmLookup can look up the charm URL for a unit tag. 71 type UnitCharmLookup interface { 72 CharmURL(names.UnitTag) (*corecharm.URL, error) 73 } 74 75 var newCollect = func(config ManifoldConfig, getResource dependency.GetResourceFunc) (*collect, error) { 76 period := defaultPeriod 77 if config.Period != nil { 78 period = *config.Period 79 } 80 81 var agent agent.Agent 82 if err := getResource(config.AgentName, &agent); err != nil { 83 return nil, err 84 } 85 tag := agent.CurrentConfig().Tag() 86 unitTag, ok := tag.(names.UnitTag) 87 if !ok { 88 return nil, errors.Errorf("expected a unit tag, got %v", tag) 89 } 90 91 var apiCaller base.APICaller 92 if err := getResource(config.APICallerName, &apiCaller); err != nil { 93 return nil, err 94 } 95 uniterFacade := uniterapi.NewState(apiCaller, unitTag) 96 97 var metricFactory spool.MetricFactory 98 err := getResource(config.MetricSpoolName, &metricFactory) 99 if err != nil { 100 return nil, err 101 } 102 103 var charmdir charmdir.Consumer 104 err = getResource(config.CharmDirName, &charmdir) 105 if err != nil { 106 return nil, err 107 } 108 109 collector := &collect{ 110 period: period, 111 agent: agent, 112 unitCharmLookup: &unitCharmLookup{uniterFacade}, 113 metricFactory: metricFactory, 114 charmdir: charmdir, 115 } 116 return collector, nil 117 } 118 119 type unitCharmLookup struct { 120 st *uniterapi.State 121 } 122 123 // CharmURL implements UnitCharmLookup. 124 func (r *unitCharmLookup) CharmURL(unitTag names.UnitTag) (*corecharm.URL, error) { 125 unit, err := r.st.Unit(unitTag) 126 if err != nil { 127 return nil, errors.Trace(err) 128 } 129 return unit.CharmURL() 130 } 131 132 type collect struct { 133 period time.Duration 134 agent agent.Agent 135 unitCharmLookup UnitCharmLookup 136 metricFactory spool.MetricFactory 137 charmdir charmdir.Consumer 138 } 139 140 var errMetricsNotDefined = errors.New("no metrics defined") 141 142 var newRecorder = func(unitTag names.UnitTag, paths context.Paths, unitCharm UnitCharmLookup, metricFactory spool.MetricFactory) (spool.MetricRecorder, error) { 143 ch, err := corecharm.ReadCharm(paths.GetCharmDir()) 144 if err != nil { 145 return nil, errors.Trace(err) 146 } 147 148 chURL, err := unitCharm.CharmURL(unitTag) 149 if err != nil { 150 return nil, errors.Trace(err) 151 } 152 153 charmMetrics := map[string]corecharm.Metric{} 154 if ch.Metrics() != nil { 155 charmMetrics = ch.Metrics().Metrics 156 } 157 if len(charmMetrics) == 0 { 158 return nil, errMetricsNotDefined 159 } 160 return metricFactory.Recorder(charmMetrics, chURL.String(), unitTag.String()) 161 } 162 163 // Do satisfies the worker.PeriodWorkerCall function type. 164 func (w *collect) Do(stop <-chan struct{}) error { 165 err := w.charmdir.Run(w.do) 166 if err == charmdir.ErrNotAvailable { 167 logger.Debugf("cannot execute collect-metrics - charmdir locked") 168 return nil 169 } 170 return err 171 } 172 173 type collectPaths struct { 174 dataDir string 175 unitTag names.UnitTag 176 } 177 178 func (p *collectPaths) GetToolsDir() string { 179 return filepath.FromSlash(tools.ToolsDir(p.dataDir, p.unitTag.String())) 180 } 181 182 func (p *collectPaths) GetCharmDir() string { 183 return filepath.Join(p.baseDir(), "charm") 184 } 185 186 func (p *collectPaths) GetJujucSocket() string { 187 if os.HostOS() == os.Windows { 188 return fmt.Sprintf(`\\.\pipe\%s-metrics`, p.unitTag) 189 } 190 return filepath.Join(p.baseDir(), "metrics.socket@") 191 } 192 193 func (p *collectPaths) GetMetricsSpoolDir() string { 194 return "" 195 } 196 197 func (p *collectPaths) baseDir() string { 198 return filepath.Join(p.dataDir, "agents", p.unitTag.String()) 199 } 200 201 func (w *collect) do() error { 202 logger.Tracef("recording metrics") 203 204 config := w.agent.CurrentConfig() 205 tag := config.Tag() 206 unitTag, ok := tag.(names.UnitTag) 207 if !ok { 208 return errors.Errorf("expected a unit tag, got %v", tag) 209 } 210 paths := &collectPaths{dataDir: config.DataDir(), unitTag: unitTag} 211 212 recorder, err := newRecorder(unitTag, paths, w.unitCharmLookup, w.metricFactory) 213 if errors.Cause(err) == errMetricsNotDefined { 214 logger.Tracef("%v", err) 215 return nil 216 } else if err != nil { 217 return errors.Annotate(err, "failed to instantiate metric recorder") 218 } 219 220 ctx := newHookContext(unitTag.String(), recorder) 221 err = ctx.addJujuUnitsMetric() 222 if err != nil { 223 return errors.Annotatef(err, "error adding 'juju-units' metric") 224 } 225 226 r := runner.NewRunner(ctx, paths) 227 err = r.RunHook(string(hooks.CollectMetrics)) 228 if err != nil { 229 return errors.Annotatef(err, "error running 'collect-metrics' hook") 230 } 231 return nil 232 }