func (hc *HopCache) writeAnnotation(wg *sync.WaitGroup, hop string, annotation *annotator.ClientAnnotations, traceStartTime time.Time, errChan chan<- error) {
	defer wg.Done()
	filepath, err := hc.generateAnnotationFilepath(hop, traceStartTime)
	if err != nil {
		errChan <- err
		return
	}
	yyyymmdd := traceStartTime.Format("20060102")
	b, err := json.Marshal(HopAnnotation1{
		ID:          fmt.Sprintf("%s_%s_%s", yyyymmdd, hostname, hop),
		Timestamp:   traceStartTime,
		Annotations: annotation,
	})
	if err != nil {
		hopAnnotationErrors.WithLabelValues("hopannotation", "marshal").Inc()
		errChan <- fmt.Errorf("%w (error: %v)", ErrMarshalAnnotation, err)
		return
	}
	if err := writeFile(filepath, b, 0444); err != nil {
		hopAnnotationErrors.WithLabelValues("hopannotation", "writefile").Inc()
		errChan <- fmt.Errorf("%w (error: %v)", ErrWriteMarshal, err)
		return
	}
	hopAnnotationOps.WithLabelValues("hopannotation", "written").Inc()
}
writeAnnotation writes the given hop annotations to a file.
package hopannotation
import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"os"
	"sync"
	"sync/atomic"
	"time"
	"github.com/m-lab/uuid-annotator/annotator"
	"github.com/m-lab/uuid-annotator/ipservice"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
)
var (
	ErrParseHopIP = errors.New("failed to parse hop IP address")
	ErrCreatePath = errors.New("failed to create directory path")
	ErrMarshalAnnotation = errors.New("failed to marshal annotation to json")
	ErrWriteMarshal = errors.New("failed to write marshaled annotation")
	hopAnnotationOps = promauto.NewCounterVec(
		prometheus.CounterOpts{
			Name: "hop_cache_operations_total",
			Help: "The number of hop cache operations",
		},
		[]string{"type", "operation"},
	)
	hopAnnotationErrors = promauto.NewCounterVec(
		prometheus.CounterOpts{
			Name: "hop_annotation_errors_total",
			Help: "The number of errors hop annotations errors",
		},
		[]string{"type", "error"},
	)
	hostname string
	tickerDuration = int64(60 * 1000 * time.Millisecond)  
	writeFile      = ioutil.WriteFile
)
type HopAnnotation1 struct {
	ID          string
	Timestamp   time.Time
	Annotations *annotator.ClientAnnotations
}
type Config struct {
	IPServiceSocket string
	OutputPath      string
}
type HopCache struct {
	hops       map[string]bool   
	hopsLock   sync.Mutex        
	annotator  ipservice.Client  
	outputPath string            
	hour       int32             
}
func init() {
	var err error
	hostname, err = os.Hostname()
	if err != nil {
		log.Fatalf("failed to get hostname (error: %v)\n", err)
	}
}
func New(ctx context.Context, haCfg Config) (*HopCache, error) {
	if haCfg.OutputPath == "" {
		return nil, fmt.Errorf("missing hop annotation output path")
	}
	hc := &HopCache{
		hops:       make(map[string]bool, 10000),  
		annotator:  ipservice.NewClient(haCfg.IPServiceSocket),
		outputPath: haCfg.OutputPath,
	}
	go func(duration time.Duration) {
		ticker := time.NewTicker(duration)
		defer ticker.Stop()
		for now := range ticker.C {
			if ctx.Err() != nil {
				return
			}
			hour := now.Hour()
			if hour < int(atomic.LoadInt32(&hc.hour)) {
				hc.Reset()
			}
			atomic.StoreInt32(&hc.hour, int32(hour))
		}
	}(time.Duration(tickerDuration))
	return hc, nil
}
func (hc *HopCache) Reset() {
	hc.hopsLock.Lock()
	defer hc.hopsLock.Unlock()
	hc.hops = make(map[string]bool, len(hc.hops)+len(hc.hops)/4)
}
func (hc *HopCache) Annotate(ctx context.Context, hops []string, traceStartTime time.Time) (map[string]*annotator.ClientAnnotations, []error) {
	if err := ctx.Err(); err != nil {
		return nil, []error{err}
	}
	allErrs := []error{}
	for _, hop := range hops {
		if net.ParseIP(hop).String() == "<nil>" {
			allErrs = append(allErrs, fmt.Errorf("%w: %v", ErrParseHopIP, hop))
		}
	}
	if len(allErrs) != 0 {
		return nil, allErrs
	}
	var newHops []string
	yyyymmdd := traceStartTime.Format("-20060102")
	hc.hopsLock.Lock()
	for _, hop := range hops {
		if !hc.hops[hop+yyyymmdd] {
			hopAnnotationOps.WithLabelValues("hopcache", "inserted").Inc()
			hc.hops[hop+yyyymmdd] = true
			newHops = append(newHops, hop)
		}
	}
	hc.hopsLock.Unlock()
	if len(newHops) == 0 {
		return nil, nil
	}
	newAnnotations, err := hc.annotator.Annotate(ctx, newHops)
	if err != nil {
		return nil, []error{err}
	}
	hopAnnotationOps.WithLabelValues("hopcache", "annotated").Add(float64(len(newAnnotations)))
	return newAnnotations, nil
}
func (hc *HopCache) WriteAnnotations(annotations map[string]*annotator.ClientAnnotations, traceStartTime time.Time) []error {
	var wg sync.WaitGroup
	errChan := make(chan error, len(annotations))
	for hop, annotation := range annotations {
		wg.Add(1)
		go hc.writeAnnotation(&wg, hop, annotation, traceStartTime, errChan)
	}
	wg.Wait()
	close(errChan)
	var allErrs []error
	for err := range errChan {
		allErrs = append(allErrs, err)
	}
	return allErrs
}