github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/worker/uniter/runner/metrics.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package runner
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/juju/errors"
    17  	"github.com/juju/utils"
    18  	"github.com/juju/utils/fslock"
    19  
    20  	"github.com/juju/juju/worker/uniter/runner/jujuc"
    21  )
    22  
    23  const spoolLockName string = "access"
    24  
    25  var lockTimeout = time.Second * 5
    26  
    27  // MetricsMetadata is used to store metadata for the current metric batch.
    28  type MetricsMetadata struct {
    29  	CharmURL string    `json:"charmurl"`
    30  	UUID     string    `json:"uuid"`
    31  	Created  time.Time `json:"created"`
    32  }
    33  
    34  // JSONMetricsRecorder implements the MetricsRecorder interface
    35  // and writes metrics to a spool directory for store-and-forward.
    36  type JSONMetricsRecorder struct {
    37  	sync.Mutex
    38  
    39  	path string
    40  
    41  	file io.Closer
    42  	enc  *json.Encoder
    43  }
    44  
    45  // NewJSONMetricsRecorder creates a new JSON metrics recorder.
    46  // It checks if the metrics spool directory exists, if it does not - it is created. Then
    47  // it tries to find an unused metric batch UUID 3 times.
    48  func NewJSONMetricsRecorder(spoolDir string, charmURL string) (rec *JSONMetricsRecorder, rErr error) {
    49  	lock, err := fslock.NewLock(spoolDir, spoolLockName)
    50  	if err != nil {
    51  		return nil, errors.Trace(err)
    52  	}
    53  	if err := lock.LockWithTimeout(lockTimeout, "initializing recorder"); err != nil {
    54  		return nil, errors.Trace(err)
    55  	}
    56  	defer func() {
    57  		err := lock.Unlock()
    58  		if err != nil && rErr == nil {
    59  			rErr = errors.Trace(err)
    60  			rec = nil
    61  		} else if err != nil {
    62  			rErr = errors.Annotatef(err, "failed to unlock spool directory %q", spoolDir)
    63  		}
    64  	}()
    65  
    66  	if err := checkSpoolDir(spoolDir); err != nil {
    67  		return nil, errors.Trace(err)
    68  	}
    69  
    70  	mbUUID, err := utils.NewUUID()
    71  	if err != nil {
    72  		return nil, errors.Trace(err)
    73  	}
    74  
    75  	metaFile := filepath.Join(spoolDir, fmt.Sprintf("%s.meta", mbUUID.String()))
    76  	dataFile := filepath.Join(spoolDir, mbUUID.String())
    77  	if _, err := os.Stat(metaFile); !os.IsNotExist(err) {
    78  		if err != nil {
    79  			return nil, errors.Annotatef(err, "failed to stat file %s", metaFile)
    80  		}
    81  		return nil, errors.Errorf("file %s already exists", metaFile)
    82  	}
    83  	if _, err := os.Stat(dataFile); err != nil && !os.IsNotExist(err) {
    84  		if err != nil {
    85  			return nil, errors.Annotatef(err, "failed to stat file %s", dataFile)
    86  		}
    87  		return nil, errors.Errorf("file %s already exists", dataFile)
    88  	}
    89  
    90  	if err := recordMetaData(metaFile, charmURL, mbUUID.String()); err != nil {
    91  		return nil, errors.Trace(err)
    92  	}
    93  
    94  	recorder := &JSONMetricsRecorder{
    95  		path: dataFile,
    96  	}
    97  	if err := recorder.open(); err != nil {
    98  		return nil, errors.Trace(err)
    99  	}
   100  	return recorder, nil
   101  }
   102  
   103  // Close implements the MetricsRecorder interface.
   104  func (m *JSONMetricsRecorder) Close() error {
   105  	m.Lock()
   106  	defer m.Unlock()
   107  	return errors.Trace(m.file.Close())
   108  }
   109  
   110  // AddMetric implements the MetricsRecorder interface.
   111  func (m *JSONMetricsRecorder) AddMetric(key, value string, created time.Time) error {
   112  	m.Lock()
   113  	defer m.Unlock()
   114  	return errors.Trace(m.enc.Encode(jujuc.Metric{Key: key, Value: value, Time: created}))
   115  }
   116  
   117  func (m *JSONMetricsRecorder) open() error {
   118  	dataWriter, err := os.Create(m.path)
   119  	if err != nil {
   120  		return errors.Trace(err)
   121  	}
   122  	m.file = dataWriter
   123  	m.enc = json.NewEncoder(dataWriter)
   124  	return nil
   125  
   126  }
   127  
   128  func checkSpoolDir(path string) error {
   129  	if _, err := os.Stat(path); os.IsNotExist(err) {
   130  		err := os.MkdirAll(path, 0755)
   131  		if err != nil {
   132  			return errors.Trace(err)
   133  		}
   134  	} else if err != nil {
   135  		return errors.Trace(err)
   136  	}
   137  	return nil
   138  }
   139  
   140  func recordMetaData(path string, charmURL, UUID string) error {
   141  	metadata := MetricsMetadata{
   142  		CharmURL: charmURL,
   143  		UUID:     UUID,
   144  		Created:  time.Now().UTC(),
   145  	}
   146  	metaWriter, err := os.Create(path)
   147  	if err != nil {
   148  		return errors.Trace(err)
   149  	}
   150  	defer metaWriter.Close()
   151  	enc := json.NewEncoder(metaWriter)
   152  	err = enc.Encode(metadata)
   153  	if err != nil {
   154  		return errors.Trace(err)
   155  	}
   156  	return nil
   157  }
   158  
   159  // MetricsBatch stores the information relevant to a single metrics batch.
   160  type MetricsBatch struct {
   161  	CharmURL string         `json:"charmurl"`
   162  	UUID     string         `json:"uuid"`
   163  	Created  time.Time      `json:"created"`
   164  	Metrics  []jujuc.Metric `json:"metrics"`
   165  }
   166  
   167  // JSONMetricsReader reads metrics batches stored in the spool directory.
   168  type JSONMetricsReader struct {
   169  	dir  string
   170  	lock *fslock.Lock
   171  }
   172  
   173  // NewJSONMetricsReader creates a new JSON metrics reader for the specified spool directory.
   174  func NewJSONMetricsReader(spoolDir string) (*JSONMetricsReader, error) {
   175  	if _, err := os.Stat(spoolDir); err != nil {
   176  		return nil, errors.Annotatef(err, "failed to open spool directory %q", spoolDir)
   177  	}
   178  	lock, err := fslock.NewLock(spoolDir, spoolLockName)
   179  	if err != nil {
   180  		return nil, errors.Trace(err)
   181  	}
   182  	return &JSONMetricsReader{
   183  		lock: lock,
   184  		dir:  spoolDir,
   185  	}, nil
   186  }
   187  
   188  // Open implements the MetricsReader interface.
   189  // Due to the way the batches are stored in the file system,
   190  // they will be returned in an arbitrary order. This does not affect the behavior.
   191  func (r *JSONMetricsReader) Open() ([]MetricsBatch, error) {
   192  	var batches []MetricsBatch
   193  
   194  	if err := r.lock.LockWithTimeout(lockTimeout, "reading"); err != nil {
   195  		return nil, errors.Trace(err)
   196  	}
   197  
   198  	walker := func(path string, info os.FileInfo, err error) error {
   199  		if err != nil {
   200  			return errors.Trace(err)
   201  		}
   202  		if info.IsDir() && path != r.dir {
   203  			return filepath.SkipDir
   204  		} else if !strings.HasSuffix(info.Name(), ".meta") {
   205  			return nil
   206  		}
   207  
   208  		batch, err := decodeBatch(path)
   209  		if err != nil {
   210  			return errors.Trace(err)
   211  		}
   212  		batch.Metrics, err = decodeMetrics(filepath.Join(r.dir, batch.UUID))
   213  		if err != nil {
   214  			return errors.Trace(err)
   215  		}
   216  		batches = append(batches, batch)
   217  		return nil
   218  	}
   219  	if err := filepath.Walk(r.dir, walker); err != nil {
   220  		return nil, errors.Trace(err)
   221  	}
   222  	return batches, nil
   223  }
   224  
   225  // Remove implements the MetricsReader interface.
   226  func (r *JSONMetricsReader) Remove(uuid string) error {
   227  	metaFile := filepath.Join(r.dir, fmt.Sprintf("%s.meta", uuid))
   228  	dataFile := filepath.Join(r.dir, uuid)
   229  	err := os.Remove(metaFile)
   230  	if err != nil && !os.IsNotExist(err) {
   231  		return errors.Trace(err)
   232  	}
   233  	err = os.Remove(dataFile)
   234  	if err != nil {
   235  		return errors.Trace(err)
   236  	}
   237  	return nil
   238  }
   239  
   240  // Close implements the MetricsReader interface.
   241  func (r *JSONMetricsReader) Close() error {
   242  	if r.lock.IsLockHeld() {
   243  		return r.lock.Unlock()
   244  	}
   245  	return nil
   246  }
   247  
   248  func decodeBatch(file string) (MetricsBatch, error) {
   249  	var batch MetricsBatch
   250  	f, err := os.Open(file)
   251  	if err != nil {
   252  		return MetricsBatch{}, errors.Trace(err)
   253  	}
   254  	defer f.Close()
   255  	dec := json.NewDecoder(f)
   256  	err = dec.Decode(&batch)
   257  	if err != nil {
   258  		return MetricsBatch{}, errors.Trace(err)
   259  	}
   260  	return batch, nil
   261  }
   262  
   263  func decodeMetrics(file string) ([]jujuc.Metric, error) {
   264  	var metrics []jujuc.Metric
   265  	f, err := os.Open(file)
   266  	if err != nil {
   267  		return nil, errors.Trace(err)
   268  	}
   269  	defer f.Close()
   270  	dec := json.NewDecoder(f)
   271  	for {
   272  		var metric jujuc.Metric
   273  		err := dec.Decode(&metric)
   274  		if err == io.EOF {
   275  			break
   276  		} else if err != nil {
   277  			return nil, errors.Trace(err)
   278  		}
   279  		metrics = append(metrics, metric)
   280  	}
   281  	return metrics, nil
   282  }